Sadece bir şey yapan bir sınıf için örnek


24

Diyelim ki bu işi yapan bir prosedürüm var :

void doStuff(initalParams) {
    ...
}

Şimdi "şeyler yapmanın" oldukça zorlayıcı bir işlem olduğunu keşfettim. Prosedür, ben birden fazla küçük prosedürler halinde bölmeli ve yakında bir tür olan fark büyük hale devletin Ben küçük prosedürler arasında daha az parametreler geçirmeniz gerekebilir böylece, malzeme yaparken faydalı olacaktır. Bu yüzden kendi sınıfına dahil ettim:

class StuffDoer {
    private someInternalState;

    public Start(initalParams) {
        ...
    }

    // some private helper procedures here
    ...
}

Sonra buna şöyle derim:

new StuffDoer().Start(initialParams);

veya bunun gibi:

new StuffDoer(initialParams).Start();

Ve bu yanlış hissettiren şey. .NET veya Java API kullanırken, asla aramam new SomeApiClass().Start(...);, bu da yanlış yaptığımdan şüphelenmeme neden oluyor. Tabii, StuffDoer'in yapıcısını özel yapabilir ve statik bir yardımcı yöntem ekleyebilirim:

public static DoStuff(initalParams) {
    new StuffDoer().Start(initialParams);
}

Ama sonra dış arayüzü sadece garip hissettiren tek bir statik yöntemden oluşan bir sınıfa sahip olurdum.

Dolayısıyla benim sorum: Bu sınıflar için sağlam bir kalıp var mı?

  • sadece bir giriş noktası olması ve
  • "harici olarak algılanabilir" bir durum yok mu, yani, örnek durumu yalnızca bir giriş noktasının yürütülmesi sırasında mı gerekli ?

Tek bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");umursadığım şeylerin belirli bir bilgi parçası olduğu ve LINQ genişletme yöntemlerinin mevcut olmadığı gibi şeyler yaptığıydı. İyi çalışır ve if()çemberin içinden atlamak zorunda kalmadan bir koşulun içine uyar . Tabii ki, eğer böyle bir kod yazıyorsanız, çöp toplanmış bir dil istiyorsunuz.
CVn

O en Eğer dil agnostik , neden eklenti java ve c # de? :)
Steven Jeuris

Merak ediyorum, bu 'tek yöntemin' işlevselliği nedir? Belirli senaryoda en iyi yaklaşımın ne olduğunu belirlemede çok yardımcı olacaktır, ancak yine de ilginç bir soru!
Steven Jeuris,

@StevenJeuris: Bu konuda çeşitli durumlarda rastladım, ancak mevcut durumda, verileri yerel veritabanına ithal eden bir yöntemdir (bir web sunucusundan yükler, bazı manipülasyonlar yapar ve veritabanına depolar).
Heinzi

1
Örneği oluştururken her zaman yöntemi çağırıyorsanız, neden bunu yapıcı içinde bir başlatma mantığı yapmıyorsunuz?
NoChance

Yanıtlar:


29

Ayrı bir sınıfa çok fazla geçici değişken / argüman içeren tek, büyük bir metodu belirlediğiniz, Object Object adında bir kalıp var . Bunu, yöntemin bölümlerini ayrı yöntemlere çıkarmak yerine, yerel duruma (parametreler ve geçici değişkenler) erişmeleri gerektiğinden ve bunu, bu değişkende yerel olacağından, örnek değişkenleri kullanarak yerel durumu paylaşamadığınız için yaparsınız. (ve ondan ayıklanan yöntemler) yalnızca ve nesnenin geri kalanı tarafından kullanılmaz.

Bunun yerine, yöntem kendi sınıfı olur, parametreler ve geçici değişkenler bu yeni sınıfın örnek değişkenleri olur ve ardından yöntem yeni sınıfın daha küçük yöntemlerine ayrılır. Sonuçta ortaya çıkan sınıf genellikle sınıfın kapsüllenen görevi yerine getiren tek bir ortak örnek yöntemine sahiptir.


1
Bu tam olarak ne yapıyorum gibi geliyor. Teşekkürler, en azından şimdi bir isim var. :-)
Heinzi

2
Çok alakalı bağlantı! Bana bir anti-patern gibi kokuyor. Eğer yönteminiz bu kadar uzunsa, belki de bölümleri yeniden kullanılabilir sınıflarda çıkarılabilir veya genellikle daha iyi bir şekilde yeniden şekillendirilebilir mi? Bu kalıp hakkında daha önce hiç duymadım, bu kadar kullanılmadığına inanmamı sağlayan başka kaynaklar da bulamıyorum.
Steven Jeuris,


1
@StevenJeuris Bunun bir anti-kalıp olduğunu sanmıyorum. Bir anti-patern, bir örnek değişkeninde bu yöntemler arasında ortak olan bu durumu depolamak yerine, refactored yöntemleri lot parametreleriyle karıştırmak olacaktır.
Alfredo Osorio

@AlfredoOsorio: Pekala, birçok parametre ile refactored yöntemleri karıştırır. Nesnede yaşarlar, bu yüzden hepsinin etrafından dolaşmaktan daha iyidir, ancak sadece her birinin parametrelerinin sınırlı bir alt kümesine ihtiyaç duyan yeniden kullanılabilir bileşenlere yeniden yapılanmadan daha kötüdür.
Jan Hudec

13

Söz konusu sınıfın (tek bir giriş noktası kullanarak) iyi tasarlanmış olduğunu söyleyebilirim. Kullanımı ve genişletilmesi kolaydır. Oldukça SOLID.

Aynı şey için no "externally recognizable" state. Bu iyi bir kapsülleme.

Gibi kod ile herhangi bir sorun görmüyorum:

var doer = new StuffDoer(initialParams);
var result = doer.Calculate(extraParams);

1
Ve tabii ki, doertekrar kullanmaya gerek yoksa , bu azalır var result = new StuffDoer(initialParams).Calculate(extraParams);.
CVn

Hesapla doStuff!
kasım

1
Sınıfınızı kullanmak istediğiniz yerde (yeni) başlatmak istemiyorsanız (muhtemelen bir Fabrika kullanmak istiyorsanız), nesneyi iş mantığınızda başka bir yerde yaratma seçeneğine ve parametreleri doğrudan iletme seçeneğine sahip olduğunuzu ekleyeceğim. Sahip olduğunuz tek kamu yöntemine. Diğer seçenek ise bir Fabrika kullanmak ve bunun üzerine parametreleri alan ve yapıcı aracılığıyla tüm paramlara karşı nesne yaratan bir make metoduna sahip olmaktır. Genel yönteminizi, istediğiniz yerden paraşüt olmadan çağırıyorsunuz.
Patkos Csaba

2
Bu cevap fazlasıyla basittir. Kullandığınız bir StringComparersınıf new StringComparer().Compare( "bleh", "blah" )iyi tasarlanmış mı? Tamamen gerekmediğinde devlet yaratmaya ne dersiniz? Tamam davranış iyi kapsüllenmiş (en azından sınıfın kullanıcısına, benim cevabımdan daha fazlası için ) ama bu varsayılan olarak 'iyi tasarım' yapmaz. 'Sonuç-sonuç' örneğinize gelince. Bu, yalnızca ondan doerayrı kullanmanız durumunda anlamlı olur result.
Steven Jeuris,

1
Var olmasının bir nedeni değil one reason to change. Farklılık ince görünebilir. Bunun ne demek olduğunu anlamalısın. Asla bir yöntem sınıfı oluşturmayacak
jgauffin

8

Bir itibaren KATI ilkeler perspektif jgauffin cevabı mantıklı. Ancak, genel tasarım ilkelerini unutmamalısınız, örneğin bilgi gizleme .

Verilen yaklaşımla ilgili birkaç problem görüyorum:

  • Sizin de belirttiğiniz gibi, insanlar 'new' anahtar sözcüğünü kullanmayı beklemiyorlar, oluşturulan nesne herhangi bir durumu yönetmiyorsa . Tasarımınız amacını yansıtıyor. Sınıfınızı kullanan kişiler, hangi durumu yönettiğini ve sonradan yönteme yapılan çağrıların farklı davranışlarla sonuçlanıp sonuçlanmayacağı konusunda karışık olabilir.
  • Sınıfı kullanan kişinin bakış açısından içsel durum oldukça gizlidir, ancak sınıfta değişiklikler yapmak veya basitçe anlamak istediğinizde, işleri daha karmaşık hale getiriyorsunuz. Ben zaten sadece onları küçültmek için ben bölme yöntemleri ile bakın problemler hakkında çok şey yazdım sınıf kapsamına durumunu hareketli, özellikle. API'nizin daha küçük fonksiyonlara sahip olması için nasıl kullanılması gerektiğini değiştiriyorsunuz! Bu bence kesinlikle çok ileri götürüyor.

İlgili bazı referanslar

Muhtemelen ana tartışma konusu, Tek Sorumluluk İlkesinin ne kadar gerileceğine yatar . “Eğer onu bu uç noktaya götürür ve varolan bir nedeni olan sınıfları inşa edersen, sınıf başına sadece bir yöntemle sonuçlanabilir. Bu, sistemin en basit süreçler için bile geniş bir sınıf yayılımına neden olur. anlaşılması zor ve değiştirilmesi zor. "

Bu konuyla ilgili bir başka ilgili referans: "Programlarınızı tanımlanabilir bir görevi yerine getiren yöntemlere bölün. Tüm işlemleri bir yöntemde aynı soyutlama düzeyinde tutun ." - Kent Beck Key burada "aynı soyutlama seviyesi" dir. Bu genellikle yorumlandığı için “bir şey” anlamına gelmez. Bu soyutlama seviyesi tamamen sizin tasarladığınız içeriğe bağlıdır .

Peki uygun yaklaşım nedir?

Somut kullanım durumunuzu bilmeden söylemek zor. Bazen (sık sık değil) benzer bir yaklaşım kullandığım bir senaryo var. Bir veri kümesini işlemek istediğimde, bu işlevselliği tüm sınıf kapsamı için kullanılabilir yapmak istemeden. Bununla ilgili bir blog yazısı yazdım, lambdaların kapsüllemeyi nasıl daha da geliştirebileceğini . Ben de Programcılar burada konu hakkında bir soru başlamıştır . Aşağıdaki, bu tekniği nerede kullandığımın son bir örneğidir.

new TupleList<Key, int>
{
    { Key.NumPad1, 1 },
            ...
    { Key.NumPad3, 16 },
    { Key.NumPad4, 17 },
}
    .ForEach( t =>
    {
        var trigger = new IC.Trigger.EventTrigger(
                        new KeyInputCondition( t.Item1, KeyInputCondition.KeyState.Down ) );
        trigger.ConditionsMet += () => AddMarker( t.Item2 );
        _inputController.AddTrigger( trigger );
    } );

İçindeki çok 'yerel' kod ForEachbaşka bir yerde tekrar kullanılmadığından, sadece ilgili olduğu yerde tutmaya devam edebilirim. Kodu, birbirlerine dayanan kodun güçlü bir şekilde gruplanmış olduğu şekilde özetlemek bence daha kolay okunur kılar.

Muhtemel alternatifler

  • C # 'da bunun yerine uzantı yöntemlerini kullanabilirsiniz. Yani argüman üzerinde doğrudan bu 'tek şey' yöntemine geçersiniz.
  • Bu işlevin aslında başka bir sınıfa ait olmadığını görün.
  • Statik bir sınıfta statik bir fonksiyon yapın . Bu en muhtemel yaklaşım, aynı zamanda atıfta bulunduğunuz genel API'lerde de yansıtıldığı gibi en uygun yaklaşımdır.

Poltergeists, başka bir cevapta belirtildiği gibi bu anti-patern için olası bir isim buldum .
Steven Jeuris,

4

Oldukça iyi olduğunu söyleyebilirim, ancak kullanıcının beklediği gibi API'nizin statik bir sınıfta statik bir yöntem olması gerekir. Statik yöntemin newyardımcı nesnenizi oluşturmak ve işi yapmak için kullanması, onu çağıran kişiden gizlenmesi gereken bir uygulama detayıdır.


Vaov! Onun eline benden çok daha net bir şekilde ulaşmayı başardın. :) Teşekkürler. (elbette aynı fikirde
olamayacağımız

2
new StuffDoer().Start(initialParams);

Paylaşmasız bir örneği kullanıp kullanabilecekleri clueless geliştiricileri ile ilgili bir sorun var

  1. birden çok kez (eğer önceki (belki de kısmi) yürütme daha sonra yürütmeyi karıştırmazsa)
  2. Birden fazla iş parçacığından (Tamam değil, açıkça iç durum olduğunu söylemiştin).

Bu nedenle, iş parçacığının güvenli olmadığı ve hafiftirse (hızlı olması, dış kaynakları veya çok fazla belleği bir araya getirmediği) açık bir şekilde belgelendirilmesi gerekiyor ve anında başlatılmasının sorun değil.

Statik bir yöntemde saklamak buna yardımcı olur, çünkü örneği tekrar kullanmak asla olmaz.

Bazı pahalı başlatma işlemlerine sahipse, (her zaman değildir) bir kez başlatmanın ve yalnızca durum içerecek başka bir sınıf oluşturarak ve klonlanabilir hale getirerek birçok kez kullanması yararlı olabilir (her zaman değildir). Başlatma, içinde depolanacak bir durum yaratacaktır doer. start()onu klonlar ve iç yöntemlere geçirirdi.

Bu, kısmi infaz halinin devam etmesi gibi başka şeylere de izin verir. (Elektrik arızası gibi uzun ve dış faktörler gerektiriyorsa, yürütmeyi kesintiye uğratabilir.) Ancak bu ek süslü şeylere genellikle ihtiyaç duyulmaz, buna değmez.


Güzel nokta. Umarım temizlikten sakıncası yoktur.
Steven Jeuris,

2

Daha ayrıntılı ve kişiselleştirilmiş diğer yanıtlarımın yanı sıra , aşağıdaki gözlemin ayrı bir cevabı hakettiğini hissediyorum.

Poltergeists anti-pattern izliyor olabileceğine dair göstergeler var .

Poltergeistler çok sınırlı rolleri ve etkili yaşam döngüleri olan sınıflardır. Genellikle başka nesneler için süreçler başlatırlar. Yeniden yapılandırılmış çözüm, Polterjistleri ortadan kaldıran daha uzun ömürlü nesnelere karşı sorumlulukların yeniden tahsis edilmesini içerir.

Belirtiler ve Sonuçlar

  • Yedek gezinme yolları.
  • Geçici dernekler.
  • Vatansız sınıflar.
  • Geçici, kısa süreli nesneler ve sınıflar.
  • Geçici dernekler aracılığıyla sadece “tohum” ya da “diğer sınıfları” içeren tek işlemlik sınıflar.
  • Start_process_alpha gibi "kontrol benzeri" işlem adlarına sahip sınıflar.

Refaktörlü Çözüm

Ghostbusters, Poltergeists'i sınıf hiyerarşisinden tamamen çıkararak çözer. Bununla birlikte, bunların kaldırılmasından sonra, poltergeist tarafından “sağlanan” işlevler değiştirilmelidir. Mimariyi düzeltmek için basit bir ayarlama ile bu kolaydır.


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.