Çok fazla statik yöntem kullanmak kötü bir şey mi?


97

Bir sınıfın iç durumları takip etmesi gerekmediğinde, bir sınıftaki tüm yöntemleri statik olarak bildirme eğilimindeyim. Örneğin, eğer A'yı B'ye dönüştürmem gerekiyorsa ve değişebilen bazı iç durum C'ye güvenmiyorsam, statik bir dönüşüm yaratırım. Ayarlamak istediğim bir dahili durum C varsa, C'yi ayarlamak için bir kurucu ekliyorum ve statik bir dönüşüm kullanmıyorum.

Statik yöntemleri aşırı kullanmamak için çeşitli öneriler (StackOverflow dahil) okudum, ancak yine de yukarıdaki genel kuralda neyin yanlış olduğunu anlayamıyorum.

Bu mantıklı bir yaklaşım mı değil mi?


Evet öyle. Şuna
yegor256

Yanıtlar:


154

İki tür yaygın statik yöntem vardır:

  • "Güvenli" bir statik yöntem her zaman aynı girdiler için aynı çıktıyı verecektir. Hiçbir globali değiştirmez ve herhangi bir sınıfın "güvenli olmayan" statik yöntemlerini çağırmaz. Esasen, sınırlı bir tür işlevsel programlama kullanıyorsunuz - bunlardan korkmayın, sorun değil.
  • "Güvenli olmayan" statik bir yöntem, genel durumu veya proxy'leri genel bir nesneye veya diğer bazı test edilemeyen davranışlara dönüştürür. Bunlar yordamsal programlamanın geri dönüşleridir ve mümkünse yeniden düzenlenmelidir.

"Güvensiz" statiğin birkaç yaygın kullanımı vardır - örneğin, Singleton modelinde - ancak onlara güzel isimler koymanıza rağmen, sadece global değişkenleri değiştiriyorsunuz. Güvenli olmayan statiği kullanmadan önce dikkatlice düşünün.


Bu tam olarak çözmem gereken problemdi - Singleton nesnelerinin kullanımı veya daha doğrusu kötüye kullanılması.
overslacked

Bu mükemmel cevap için teşekkür ederim. Sorum şu, eğer singletonlar statik yöntemlere parametre olarak aktarılırsa, bu statik yöntemi güvensiz yapar mı?
Tony D

1
"Saf işlev" ve "saf olmayan işlev" terimleri, işlevsel programlamada "güvenli" ve "güvensiz" statik olarak adlandırdığınız şeye verilen adlardır.
Omnimike

19

İç durumu olmayan bir nesne şüpheli bir şeydir.

Normalde, nesneler durumu ve davranışı içerir. Yalnızca davranışı özetleyen bir nesne tuhaftır. Bazen Hafif veya Flyweight'in bir örneğidir .

Diğer zamanlarda, bir nesne dilinde yapılan prosedürel tasarımdır.


6
Ne dediğini duyuyorum ama Math nesnesi gibi bir şey davranıştan başka her şeyi nasıl kapsayabilir?
JonoW

10
Sadece şüpheli dedi, yanlış değil ve kesinlikle haklı.
Bill K

2
@JonoW: Matematik, birçok durumsuz işlevin olduğu çok özel bir durumdur. Elbette, Java'da İşlevsel programlama yapıyorsanız, birçok durumsuz işleviniz olur.
S.Lott

14

Bu gerçekten sadece John Millikin'in harika cevabının devamı niteliğindedir.


Durum bilgisi olmayan yöntemleri (hemen hemen işlevler olan) statik hale getirmek güvenli olsa da, bazen değiştirilmesi zor olan birleştirmeye yol açabilir. Statik bir yönteminiz olduğunu düşünün:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Hangi adla adlandırıyorsunuz:

StaticClassVersionOne.doSomeFunkyThing(42);

Statik yöntemin davranışını değiştirmeniz gereken bir durumla karşılaşıncaya ve sıkı sıkıya bağlı olduğunuzu anlayıncaya kadar her şey yolunda, iyi ve çok kullanışlıdır StaticClassVersionOne. Muhtemelen kodu değiştirebilirdiniz ve sorun olmazdı, ancak eski davranışa bağlı başka arayanlar varsa, bunların yöntemin gövdesinde hesaba katılması gerekecektir. Bazı durumlarda, bu yöntem gövdesi, tüm bu davranışları dengelemeye çalışırsa oldukça çirkin veya sürdürülemez hale gelebilir. Yöntemleri ayırırsanız, hesaba katmak için birkaç yerde kodu değiştirmeniz veya yeni sınıflara çağrı yapmanız gerekebilir.

Ancak yöntemi sağlamak için bir arayüz oluşturup onu arayanlara verdiyseniz, şimdi davranışın değişmesi gerektiğinde, arayüzü uygulamak için daha temiz, daha kolay test edilen ve daha bakımı daha kolay olan yeni bir sınıf oluşturulabilir. ve bunun yerine arayanlara verilir. Bu senaryoda, çağıran sınıfların değiştirilmesi, hatta yeniden derlenmesi gerekmez ve değişiklikler yerelleştirilir.

Olası bir durum olabilir veya olmayabilir, ancak dikkate değer olduğunu düşünüyorum.


5
Bunun sadece olası bir senaryo olmadığını, bu durumun statiği son çare haline getirdiğini savunuyorum. İstatistikler TDD'yi bir kabusa dönüştürür. Statiği nerede kullanırsanız kullanın, alay edemezsiniz, ilgisiz bir sınıfı test etmek için girdi ve çıkışın ne olduğunu bilmeniz gerekir. Şimdi, durağan davranışını değiştirirseniz, bu statik kullanan ilgisiz sınıflar üzerindeki testleriniz bozulur. Ayrıca, geliştiricileri potansiyel olarak önemli bir bağımlılık konusunda bilgilendirmek için yapıcıya aktaramayacağınız gizli bir bağımlılık haline gelir.
DanCaveman

6

Diğer seçenek, bunları kaynak nesneye statik olmayan yöntemler olarak eklemektir:

yani, değiştirme:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

içine

public class Bar {
    ...
    public Foo transform() { ...}
}

ancak birçok durumda bu mümkün değildir (örneğin, XSD / WSDL / vb. den düzenli sınıf kodu üretimi) veya sınıfı çok uzun hale getirecektir ve dönüştürme yöntemleri genellikle karmaşık nesneler için gerçek bir acı olabilir ve siz sadece onları istersiniz kendi ayrı sınıflarında. Yani evet, fayda sınıflarında statik yöntemlerim var.


5

Statik sınıflar, doğru yerlerde kullanıldıkları sürece iyidir.

Yani: 'yaprak' yöntemler olan yöntemler (durumu değiştirmezler, sadece girdiyi bir şekilde dönüştürürler). Bunun güzel örnekleri Path.Combine gibi şeylerdir. Bu tür şeyler yararlıdır ve daha ayrıntılı sözdizimi sağlar.

Sorunlar Ben statiği ile olan çoktur:

İlk olarak, statik sınıflarınız varsa, bağımlılıklar gizlidir. Aşağıdakileri göz önünde bulundur:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

TextureManager'a baktığınızda, bir kurucuya bakarak hangi başlatma adımlarının gerçekleştirilmesi gerektiğini söyleyemezsiniz. Bağımlılıklarını bulmak ve şeyleri doğru sırada başlatmak için sınıfa girmelisiniz. Bu durumda, ResourceLoader'ın çalıştırılmadan önce başlatılması gerekir. Şimdi bu bağımlılık kabusunu büyütün ve muhtemelen ne olacağını tahmin edebilirsiniz. Açık bir başlatma sırasının olmadığı bir yerde kodu korumaya çalıştığınızı hayal edin. Bunu örneklerle bağımlılık enjeksiyonu ile karşılaştırın - bu durumda , bağımlılıklar yerine getirilmezse kod derlenmez !

Dahası, durumu değiştiren statiği kullanırsanız, bu bir kartlar evi gibidir. Kimin neye erişimi olduğunu asla bilemezsiniz ve tasarım bir spagetti canavarına benzeme eğilimindedir.

Son olarak ve en önemlisi, statiği kullanmak bir programı belirli bir uygulamaya bağlar. Statik kod, test edilebilirlik için tasarım yapmanın antitezidir. Statikle dolu kodu test etmek bir kabustur. Statik bir çağrı asla bir çift test için değiştirilemez (özellikle statik türleri alay etmek için tasarlanmış test çerçevelerini kullanmadığınız sürece), bu nedenle statik bir sistem, onu kullanan her şeyin anında bir entegrasyon testi olmasına neden olur.

Kısacası, statik bazı şeyler için iyidir ve küçük araçlar veya atılan kodlar için kullanımlarını engellemem. Ancak bunun ötesinde, bakım kolaylığı, iyi tasarım ve test kolaylığı için kanlı bir kabustur.

İşte problemlerle ilgili güzel bir makale: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/


4

Statik yöntemlerden uzak durmanızın nedeni, onları kullanmanın nesnelerin avantajlarından birini kaybetmesidir. Nesneler, veri kapsüllemeye yöneliktir. Bu, böcekleri önleyen beklenmedik yan etkilerin olmasını önler. Statik yöntemlerin kapsüllenmiş verileri * yoktur ve bu nedenle bu avantajı sağlamayın.

Bununla birlikte, dahili verileri kullanmıyorsanız, bunları kullanmakta ve yürütmek biraz daha hızlıdır. Yine de içlerindeki küresel verilere dokunmadığınızdan emin olun.

  • Bazı dillerde, verilerin kapsüllenmesine ve statik yöntemlere izin veren sınıf düzeyinde değişkenler de vardır.

4

Bu makul bir yaklaşım gibi görünüyor. Çok fazla statik sınıf / yöntem kullanmak istememenizin nedeni, sonunda nesne yönelimli programlamadan uzaklaşıp, yapılandırılmış programlama alanına geçmenizdir.

Basitçe A'yı B'ye dönüştürdüğünüz durumda, tüm yaptığımızın metni metinden gitmek için dönüştürmek olduğunu söyleyin.

"hello" =>(transform)=> "<b>Hello!</b>"

O zaman statik bir yöntem mantıklı olacaktır.

Bununla birlikte, bu statik yöntemleri bir nesne üzerinde sık sık çağırıyorsanız ve birçok çağrı için benzersiz olma eğilimindeyse (örneğin, onu kullanma şekliniz girdiye bağlıdır) veya nesnenin doğal davranışının bir parçasıysa, onu nesnenin bir parçası yapmak ve bir halini korumak için akıllıca olun. Bunu yapmanın bir yolu, onu bir arayüz olarak uygulamaktır.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Düzenleme: Statik yöntemlerin harika kullanımının iyi bir örneği, Asp.Net MVC veya Ruby'deki html yardımcı yöntemleridir. Bir nesnenin davranışına bağlı olmayan ve bu nedenle statik olan html öğeleri oluştururlar.

Düzenleme 2: Fonksiyonel programlamayı yapısal programlamaya değiştirdi (nedense kafam karıştı), bunu işaret ettiği için Torsten'e destek oluyor.


2
Statik yöntemler kullanmanın işlevsel programlama olarak nitelendirildiğini sanmıyorum, bu yüzden yapısal programlamayı kastettiğinizi tahmin ediyorum.
Torsten Marek

3

Yakın zamanda, başlangıçta statik sınıflar olarak uygulanan bazı sınıfları kaldırmak / değiştirmek için bir uygulamayı yeniden düzenledim. Zamanla bu sınıflar çok şey kazandılar ve insanlar yeni işlevleri statik olarak etiketlemeye devam ettiler, çünkü etrafta dolaşan bir örnek yoktu.

Bu yüzden cevabım, statik sınıfların doğası gereği kötü olmadığı, ancak örnekleri oluşturmaya şimdi başlamak daha kolay olabilir, daha sonra yeniden düzenleme yapmak zorunda kalırsınız.


3

Bunu bir tasarım kokusu olarak kabul ediyorum. Kendinizi çoğunlukla statik yöntemler kullanırken bulursanız, muhtemelen çok iyi bir OO tasarımınız yoktur. Mutlaka kötü değil, ama tüm kokularda olduğu gibi bu beni durdurup yeniden değerlendirmeme neden olur. Daha iyi bir OO tasarımı yapabileceğinizi veya belki de diğer yöne gitmeniz ve bu problem için OO'dan tamamen kaçınmanız gerektiğini ima ediyor.


2

Bir grup statik yöntemle bir sınıf ve bir tekil arasında gidip gelirdim. Her ikisi de sorunu çözer, ancak tekli birden fazlasıyla çok daha kolay değiştirilebilir. (Programcılar her zaman o kadar emin görünüyorlar ki bir şeyden sadece 1 tane olacağım ve kendimi çok sınırlı bazı durumlar dışında statik yöntemlerden tamamen vazgeçecek kadar hatalı buldum).

Her neyse, singleton size daha sonra farklı bir örnek almak için bir şeyi fabrikaya geçirme yeteneği verir ve bu, yeniden düzenleme yapmadan tüm programınızın davranışını değiştirir. Global bir statik yöntemler sınıfını, farklı "destek" verisi olan bir şeye veya biraz farklı bir davranışa (çocuk sınıfı) dönüştürmek, büyük bir sıkıntıdır.

Ve statik yöntemlerin benzer bir avantajı yoktur.

Yani evet, kötüler.


1

İç durum devreye girmediği sürece, bu sorun değil. Genellikle statik yöntemlerin iş parçacığı açısından güvenli olması beklendiğinden, yardımcı veri yapıları kullanıyorsanız, bunları iş parçacığı açısından güvenli bir şekilde kullanın.


1

C'nin iç durumunu asla kullanmayacağınızı biliyorsanız , sorun değil. Gelecekte bu durum değişirse, yöntemi statik olmayan hale getirmeniz gerekir. Başlangıçta statik değilse, ihtiyacınız yoksa iç durumu göz ardı edebilirsiniz.


1

Bir fayda yöntemiyse, onu statik yapmak güzel. Guava ve Apache Commons bu ilke üzerine inşa edilmiştir.

Bu konudaki fikrim tamamen pragmatik. Uygulamanızın koduysa, statik yöntemler genellikle sahip olunacak en iyi şey değildir. Statik yöntemlerin ciddi birim testi sınırlamaları vardır - kolayca alay edilemezler: sahte bir statik işlevselliği başka bir teste enjekte edemezsiniz. Ayrıca, genellikle statik bir yönteme işlevsellik enjekte edemezsiniz.

Bu yüzden uygulama mantığımda genellikle küçük statik yardımcı program benzeri yöntem çağrılarım var. Yani

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

faydalarından biri de bu tür yöntemleri test etmemem :-)


1

Elbette gümüş kurşun yok. Statik sınıflar, küçük araçlar / yardımcılar için uygundur. Ancak iş mantığı programlaması için statik yöntemler kullanmak kesinlikle kötüdür. Aşağıdaki kodu düşünün

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

İçin Statik bir yöntem çağrısı bkz ItemsProcessor.SplitItem(newItem);O neden kokuyor

  • Açıkça bildirilmiş bir bağımlılığınız yok ve kodun içine girmezseniz, sınıfınız ile statik yöntem kapsayıcınız arasındaki bağlantıyı gözden kaçırabilirsiniz.
  • Bunu BusinessServiceizole etmeyi test edemezsiniz ItemsProcessor(çoğu test aracı statik sınıflarla dalga geçmez) ve birim testini imkansız hale getirir. Birim Testi Yok == düşük kalite

0

Statik yöntemler genellikle durum bilgisiz kod için bile kötü bir seçimdir. Bunun yerine, bu yöntemlerle bir kez başlatılan ve yöntemleri kullanmak isteyen bu sınıflara enjekte edilen bir singleton sınıfı oluşturun. Bu tür sınıfların alay edilmesi ve test edilmesi daha kolaydır. Çok daha nesne odaklıdırlar. Gerektiğinde bunları bir proxy ile sarmalayabilirsiniz. Statikler OO'yu çok daha zor hale getiriyor ve neredeyse her durumda onları kullanmak için bir neden göremiyorum. % 100 değil, neredeyse tamamı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.