Singleton desenine alternatifler


27

Singleton modeli hakkında farklı görüşler okudum. Bazıları, her ne pahasına olursa olsun kaçınılması gerektiğini, bazılarında ise belirli durumlarda yararlı olabileceğini savunuyor.

Singletons kullandığım bir durum, belirli bir A sınıfının nesnelerini oluşturmak için bir fabrikaya (F tipi bir f nesnesi diyelim) ihtiyacım olduğu zamandır. A tipi başlatıldı. Böylece, A'yı başlatmak isteyen kodun her bir parçası, singleton f'yi alır ve yeni örneği yaratır.

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

Yani general benim senaryom şudur

  1. En iyi duruma getirme nedenlerinden (birden fazla fabrika nesnesine ihtiyacım yok) veya ortak durumu paylaşmak için yalnızca bir sınıf örneğine ihtiyacım var (örneğin, fabrika hala kaç tane A örneği oluşturabileceğini biliyor)
  2. Kodun farklı yerlerinde F'nin bu örneğine erişebilmek için bir yola ihtiyacım var.

Ben am ilgilenen değil bu model iyi ya da kötü olup olmadığı tartışmaya, ama a tek kullanmaktan kaçınmak istiyor varsayarak başka hangi model kullanabilir miyim?

Sahip olduğum fikirler (1) fabrika nesnesini bir sicilden almak için ya da (2) fabrikayı program başlangıcında bir noktada oluşturmak ve ardından fabrikayı bir parametre olarak dolaştırmaktı.

Çözüm (1) 'de kayıt defterinin kendisi bir tekildir, bu yüzden fabrikadan kayıt defterine bir tekil kullanmama problemini değiştirdim.

(2) Durumda, fabrika nesnesinin geldiği bir başlangıç ​​kaynağına (nesnesine) ihtiyacım var, bundan dolayı tekrar başka bir singleton'a (fabrika örneğimi sağlayan nesne) geri döneceğime korkuyorum. Bu singleton zincirini takip ederek problemi diğer tüm singletonların doğrudan veya dolaylı olarak yönetildiği bir singleton'a (bütün başvuru) azaltabilirim .

Bu son seçenek (diğer tüm benzersiz nesneleri yaratan ve tüm diğer singletonları doğru yerlere enjekte eden bir ilk singleton kullanmak) kabul edilebilir bir çözüm olabilir mi? Bu, kişi tekil ton kullanmamanızı önerdiğinde dolaylı olarak önerilen bir çözüm mü, yoksa yukarıda gösterilen örnekte başka çözümler neler midir?

DÜZENLE

Sorumun amacının bazıları tarafından yanlış anlaşıldığını düşündüğümden, işte biraz daha bilgi var. Örneğin burada açıklandığı gibi , singleton kelimesi (a) tek bir örnek nesnesi olan bir sınıfı ve (b) böyle bir nesneyi oluşturmak ve ona erişmek için kullanılan bir tasarım modelini belirtebilir.

İşleri netleştirmek için (a) için benzersiz nesne terimini , (b) için singleton desenini kullanmamıza izin verin . Bu yüzden, singleton paterni ve bağımlılık enjeksiyonunun ne olduğunu biliyorum (BTW, son zamanlarda üzerinde çalıştığım bazı kodlardan singleton paterninin örneklerini kaldırmak için DI'yi yoğun bir şekilde kullanıyorum).

Demek istediğim, tüm nesne grafiği ana yöntemin yığında yaşayan tek bir nesneden başlatılmadığı sürece, her zaman benzersiz nesnelere tekil desen üzerinden erişmeye ihtiyaç duyulacak.

Benim sorum, tam nesne grafiği oluşturma ve kablolamanın olmasının ana yönteme bağlı olup olmadığına (ör. Kalıbı kullanmayan güçlü bir DI çerçeve aracılığıyla) tek tekil kalıpsız çözümdür.


8
Bağımlılık Enjeksiyonu ...
Falcon

1
@ Yonca: Bağımlılık Enjeksiyonu ... ne? DI bu sorunu çözmek için hiçbir şey yapmaz, ancak çoğu zaman size bunu gizlemenin kullanışlı bir yolunu verir.
pdr

@ Focon demek IoC konteyner? Bu, bağımlılıkları tek bir satırda çözmenize olanak sağlar. Örneğin, Bağımlılık. Çöz <IFoo> (); veya Bağımlılık. Çöz <IFoo> () .DoSomething ();
CodeART

Bu oldukça eski bir soru ve zaten cevaplandı. Yine de bunu bağlamak daha iyi olurdu: stackoverflow.com/questions/162042/…
TheSilverBullet

Yanıtlar:


7

İkinci seçeneğiniz, gitmenin iyi bir yoludur - tekil tonlardan ve global değişkenlerden kaçınmak istediğinizde, programınızdaki durumu paylaşmak için kullanılan model olan bir tür bağımlılık enjeksiyonu.

Bir şeyin fabrikanızı yaratması gerektiği gerçeğini çözemezsiniz. Bu bir şey uygulama olur ise, öyle olsun. Önemli olan, fabrikanızın hangi nesneyi yarattığına aldırmaması ve fabrikayı alan nesnelerin fabrikanın nereden geldiğine bağlı olmaması gerektiğidir. Nesnelerinizin uygulama tekiline bir işaretçi koymasını ve fabrikadan sormasını istemeyin; Uygulamanızın fabrikayı kurmasını ve ihtiyaç duyacağı nesnelere vermesini sağlayın.


17

Bir singleton'un amacı, yalnızca bir vakanın belirli bir alanda var olabileceğini zorlamaktır. Bu, tekil davranışı zorlamak için güçlü nedenleriniz varsa, bir tekiltonun yararlı olduğu anlamına gelir; pratikte, bu nadiren olsa da, çoklu işlem ve çoklu iş parçacığı kesinlikle 'benzersiz' anlamını bulanıklaştırıyor - bu, makine başına, işlem başına, istek başına, bir talep mi? Singleton uygulamanız yarış koşullarıyla ilgileniyor mu?

Singleton yerine, ikisini de kullanmayı tercih ederim:

  • kısa ömürlü yerel durumlar, örneğin bir fabrika için: tipik fabrika sınıfları, eğer varsa asgari miktarda devlete sahiptir ve amaçlarına hizmet ettikten sonra onları hayatta tutmak için gerçek bir neden yoktur; sınıf oluşturma ve silme yükü, tüm gerçek dünya senaryolarının% 99'unda endişelenecek bir şey değildir
  • Örneğin bir kaynak yöneticisi için bir örneği iletmek: bunların uzun ömürlü olması gerekir, çünkü kaynakların yüklenmesi pahalıdır ve bunları bellekte tutmak istiyorsunuz, ancak daha fazla örnek oluşturulmasını önlemek için kesinlikle hiçbir neden yoktur - kim bilir, Belki birkaç ay sonra ikinci bir kaynak yöneticisine sahip olmak mantıklı olacaktır ...

Bir singletonun kılık değiştirmiş bir küresel durum olmasının nedeni, uygulama boyunca yüksek derecede bir bağlantı sağlaması anlamına gelir - kodunuzun herhangi bir kısmı, singleton örneğini, en az çabayla her yerden alabilir. Yerel nesneler kullanırsanız veya etrafınızdaki örnekleri geçirirseniz, çok daha fazla kontrole sahip olursunuz ve kapsamlarınızı küçük ve bağımlılıklarınızı daraltabilirsiniz.


3
Bu çok iyi bir cevap ama neden singleton desenini kullanmam gerektiğini / kullanmamam gerektiğini sormuyordum. Birinin küresel devletle yaşayabileceği sıkıntıları biliyorum. Her neyse, sorunun konusu nasıl benzersiz nesnelere ve yine de tekil-kalıpsız bir uygulamaya sahip olduğuydu.
Giorgio

3

Çoğu insan (siz dahil) Singleton modelinin gerçekte ne olduğunu tamamen yanlış anlıyor. Singleton modeli yalnızca bir sınıfın bir örneğinin var olduğu ve bu örneğe bir başvuru almak için uygulamanın her yerinde kod için bazı mekanizmalar olduğu anlamına gelir.

GoF kitabında, statik bir alana referans veren statik fabrika metodu, bu mekanizmanın nasıl göründüğüne ve ciddi dezavantajlara sahip bir örnekti . Maalesef, herkes ve köpeği bu mekanizmaya dayandı ve Singleton'un neyle ilgili olduğunu düşündüler.

Alıntı yaptığınız alternatifler aslında referanslar için farklı bir mekanizmaya sahip olan Singletons'dur. (2), çağrı yığınının kökünün yakınında sadece birkaç yerde referansa ihtiyacınız olmadıkça, açıkça etrafta çok fazla geçiş yapılmasıyla sonuçlanır. (1) kaba bir bağımlılık enjeksiyon çerçevesi gibi görünüyor - öyleyse neden birini kullanmıyorsun?

Bunun avantajı, mevcut DI çerçevelerinin esnek, güçlü ve iyi test edilmiş olmalarıdır. Singletons'ı yönetmekten çok daha fazlasını yapabilirler. Bununla birlikte, yeteneklerini tam olarak kullanmak için, uygulamanızı her zaman mümkün olmayan belirli bir şekilde yapılandırırsanız, en iyi şekilde çalışırlar: ideal olarak, DI çerçevesinden elde edilen ve tüm bağımlılıklarının geçişli bir şekilde doldurulmuş ve sonra idam edildi.

Düzenleme: Sonuçta, her şey yine de ana yönteme bağlıdır. Ama haklısın: tamamen global / statik kullanımdan kaçınmanın tek yolu, her şeyi ana yöntemden almaktır. DI'nin ana yöntemin opak olduğu ve sunucuyu kurduğu sunucu ortamlarında en popüler olduğunu ve uygulama kodunun temel olarak başlatılan ve sunucu kodu tarafından çağrılan geri aramalardan oluştuğunu unutmayın. Ancak çoğu DI çerçevesi aynı zamanda "özel durumlar" için merkezi kayıt defterlerine, örneğin Spring'in ApplicationContext dosyasına doğrudan erişime izin verir.

Temel olarak, insanların bugüne kadar elde ettikleri en iyi şey, bahsettiğiniz iki alternatifin akıllıca bir kombinasyonudur.


Temel fikir bağımlılığı enjeksiyonunun temelde yukarıda çizdiğim ilk yaklaşım (örneğin bir kayıt defteri kullanmak), yani temel fikir mi demek istiyorsun?
Giorgio

@Giorgio: DI her zaman böyle bir kayıt defteri içerir, ancak bunu daha iyi yapan asıl nokta, bir nesneye ihtiyaç duyduğunuz her yerde kayıt defterini sorgulamak yerine, yukarıda yazdığım gibi geçişli olarak yerleştirilen merkezi nesnelere sahip olmanızdır.
Michael Borgwardt

@Giorgio: Somut bir örnekle anlaşılması daha kolay: Diyelim ki bir Java EE uygulamanız var. Ön uç mantığınız bir JSF Managed Bean'in eylem yönteminde. Kullanıcı bir düğmeye bastığında, JSF kabı Managed Bean'in bir örneğini oluşturur ve işlem yöntemini çağırır. Ancak bundan önce, DI bileşeni Managed Bean sınıfının alanlarına bakar ve belirli bir ek açıklamaya sahip olanlar DI konfigürasyonuna göre bir Servis nesnesine referansla doldurulur ve bu Servis nesneleri kendilerine aynı şeyi yapar . Eylem yöntemi sonra hizmetleri çağırabilir.
Michael Borgwardt

Bazı insanlar (siz dahil) soruları dikkatlice okumaz ve gerçekte ne sorulduğunu yanlış anlarlar.
Giorgio

1
@Giorgio: Asıl sorunuzdaki hiçbir şey bağımlılık enjeksiyonunu zaten bildiğinizi göstermedi. Ve güncellenmiş cevabı gör.
Michael Borgwardt

3

Birkaç alternatif var:

Bağımlılık Enjeksiyonu

Her nesnenin yaratıldığı zaman kendine bağımlılıkları geçer. Tipik olarak, nesnelerin oluşturulmasından ve kablolanmasından ya bir çerçeve ya da az sayıda fabrika sınıfı sorumludur.

Servis Sicili

Her nesneye bir servis kayıt defteri nesnesi iletilir. Farklı hizmetler sunan çeşitli nesneler isteme yöntemleri sağlar. Android çerçevesi bu kalıbı kullanır

Kök Müdürü

Nesne grafiğinin kökü olan tek bir nesne var. Bu nesnenin bağlantılarını takip ederek sonunda başka bir nesne bulunabilir. Kod şöyle görünmeye meyillidir:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

Alay etmenin dışında, DI'nin bir singleton kullanmaktan ne gibi üstünlüğü var? Bu beni çok uzun zamandır rahatsız eden bir soru. Kayıt defteri ve kök yönetici gelince, ya bir singleton ya da DI yoluyla değil mi? (Aslında, IoC konteyner olup , doğru bir kayıt defteri?)
Konrad Rudolph

1
@KonradRudolph, DI ile bağımlılıklar açıktır ve nesne, belirli bir kaynağın nasıl sağlandığına dair hiçbir varsayımda bulunmaz. Singleton'larda bağımlılıklar örtüktür ve her bir kullanım yalnızca böyle bir nesnenin olacağı varsayımını yapar.
Winston Ewert

@KonradRudolph, Singletons veya DI kullanılarak uygulanan diğer yöntemler mi? Evet ve Hayır. Sonunda, nesneleri nesnelerden birine aktarmanız veya nesneleri genel durumda saklamanız gerekir. Ancak bunu nasıl yaptığınıza dair ince farklılıklar var. Yukarıda belirtilen üç versiyonun tümü küresel devlet kullanmaktan kaçınıyor; ancak bitiş kodunun referansları alma şekli farklıdır.
Winston Ewert

Singleton kullanımı, singleton bir arabirim uygularsa (ya da olmasa bile) tek bir nesne kabul etmez ve singleton örneğini Singleton::instanceher yerde istemci kodunda sorgulamak yerine yöntemlere iletirsiniz .
Konrad Rudolph

@KonradRudolph, o zaman gerçekten hibrit Singleton / DI yaklaşımını kullanıyorsunuz. Saf tarafım, saf bir DI yaklaşımı için gitmen gerektiğini düşünüyor. Ancak, singleton ile ilgili sorunların çoğu orada gerçekten geçerli değildir.
Winston Ewert

1

Eğer singleton'daki ihtiyaçlarınız tek bir fonksiyona getirilebiliyorsa, neden sadece basit bir fabrika fonksiyonunu kullanmıyorsunuz? Genel fonksiyonlar (muhtemelen örneğinizde F sınıfı statik bir yöntemdir), doğası gereği tekilleyici ve derleyici ve bağlayıcı tarafından uygulanan benzersizdir.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

Kuşkusuz, bu, singleton'ın çalışması tek bir işlev çağrısına indirgenemediğinde bozulur, belki de küçük bir ilişkili işlevler kümesi sizi başarabilir.

Bütün bunlar söylendiğinde, sorunuzdaki 1. ve 2. maddeler, gerçekten bir şey istediğinizi açıkça belirtir. Singleton paterni tanımınıza bağlı olarak, zaten kullanıyorsunuz ya da çok yakınsınız. Singleton olmadan veya en azından birine çok yakın bir şeyden birinin olabileceğini sanmıyorum. Singleton'un anlamına çok yakın.

Önerdiğiniz gibi, bir noktada bir şeylerden birine sahip olmalısınız, bu yüzden belki de sorun, bir şeyin tek bir örneğine sahip olmamakla birlikte, kötüye kullanımı önlemek (veya en azından caydırmak veya en aza indirmek) için adımlar atmaktır. "Singleton" dan çıkıp olabildiğince parametrelere dönüştürmek iyi bir başlangıçtır. Arayüzü singleton'a daraltmak da yardımcı olur, çünkü bu şekilde kötüye kullanım için daha az fırsat vardır. Bazen onu emmek ve singleton'ı yığın veya dosya sistemi gibi çok sağlam hale getirmek zorundasınız.


4
Şahsen ben bunu singleton modelinin farklı bir uygulaması olarak görüyorum. Bu durumda, singleton örneği sınıfın kendisidir. Spesifik olarak: “gerçek” singleton ile tamamen aynı dezavantajlara sahiptir.
Joachim Sauer

@Randall Cook: Bence cevabınız benim için bir şey ifade ediyor. Singleton'un çok kötü olduğunu ve ne pahasına olursa olsun kaçınılması gerektiğini çok sık okudum. Buna katılıyorum, ancak diğer taraftan teknik olarak her zaman kaçınmanın imkansız olduğunu düşünüyorum. "Birinden birine" ihtiyacınız olduğunda onu bir yerde yaratmalı ve bir şekilde erişmelisiniz.
Giorgio

1

C ++ veya Python gibi bir çok paradigma dili kullanıyorsanız, bir singleton sınıfına alternatif, bir ad alanına sarılmış bir dizi işlev / değişkendir.

Kavramsal olarak konuşursak, serbest global değişkenler, serbest global işlevler ve bilgi gizleme için kullanılan statik değişkenler içeren bir C ++ dosyası, hepsi bir ad alanına sarılmış olarak, size neredeyse bir singleton "class" ile aynı etkiyi veriyor.

Miras almak istiyorsanız sadece bozulur. Bu şekilde daha iyi olacak bir sürü tekton gördüm.


0

Singletons Kapsülleme

Dava seneryosu. Uygulamanız Nesne Yönelimlidir ve daha fazla sınıfınız olsa bile, 3 veya 4 özel tekilleştirme gerektirir.

Örnek Öncesinde (C ++ pseudocode gibi):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

Bunlardan biri, bazı (küresel) tekilleri kısmen kaldırmaktır, hepsini, diğer tekilleri olan üyelere sahip olan benzersiz bir tekil haline getirerek.

Bu şekilde, "birkaç singleton, yaklaşım, karşı, hiç singleton yok, yaklaşım" yerine "tüm singletonları tek bir yaklaşımda kapsıyoruz".

Örnekten Sonra (C ++ benzeri sahte kod):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

Lütfen örneklerin sahte kod gibi olduklarını ve küçük hataları veya sözdizimi hatalarını göz ardı ettiklerini ve sorunun çözümünü göz ardı ettiklerini unutmayın.

Dikkate alınması gereken başka bir şey de var, çünkü kullanılan programlama dili, tekil veya tekil olmayan uygulamalarınızı nasıl uygulayacağınızı etkileyebilir.

Şerefe.


0

Orijinal gereksinimleriniz:

Yani general benim senaryom şudur

  1. Optimizasyon nedenlerinden dolayı (> çok sayıda fabrika nesnesine ihtiyacım yok) veya ortak durumu paylaşmak için (örneğin fabrika hala kaç tane A> örneği yaratabileceğini bilir) yalnızca bir sınıf örneğine ihtiyacım var
  2. Kodun farklı yerlerinde F'nin bu örneğine erişebilmek için bir yola ihtiyacım var.

Bir singleton tanımına (ve daha sonra neyi kastettiğinize) uyma. GoF'tan (sürümüm 1995) sayfa 127:

Bir sınıfın yalnızca bir örneği olduğundan emin olun ve buna genel bir erişim noktası sağlayın.

Yalnızca bir örneğe ihtiyacınız varsa, bu daha fazlasını yapmanıza engel olmaz .

Tek, küresel olarak erişilebilir bir örnek istiyorsanız, tek bir küresel olarak erişilebilir örnek oluşturun . Yaptığınız her şey için bir şablonun olmasına gerek yoktur. Ve evet, küresel olarak erişilebilir tek örnekler genellikle kötüdür. Onlara bir isim vermek onları daha az kötü yapmaz .

Küresel erişilebilirliği önlemek için, ortak “bir örnek verin ve birlikte geçirin” ya da “iki nesnenin sahibinin onları birbirine yapıştırmasını sağlayın” yaklaşımları iyi sonuç veriyor.


0

IoC kabını kullanmaya ne dersiniz? Bazı düşüncelere dayanarak şu satırlarda bir şeyler bulabilirsin:

Dependency.Resolve<IFoo>(); 

IFooBaşlangıçta hangi uygulamanın kullanılacağını belirtmeniz gerekir , ancak bu daha sonra kolayca değiştirebileceğiniz bir yapılandırmadır. Çözülen bir örneğin ömrü normalde bir IoC kabı tarafından kontrol edilebilir.

Statik singleton void yöntemi ile değiştirilebilir:

Dependency.Resolve<IFoo>().DoSomething();

Statik singleton getter yöntemi ile değiştirilebilir:

var result = Dependency.Resolve<IFoo>().GetFoo();

Örnek .NET ile ilgilidir, ancak diğer dillerde de çok benzer sonuçlara ulaşılabileceğinden eminim.

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.