Yani Singletons kötü, o zaman ne?


553

Son zamanlarda Singletons kullanma (ve aşırı kullanma) problemleri hakkında çok fazla tartışma yapıldı. Daha önce de kariyerimde o insanlardan biriydim. Şimdi sorunun ne olduğunu görebiliyorum ve yine de hoş bir alternatif göremediğim birçok durum var - ve Singleton karşıtı tartışmaların çoğu gerçekten bir tane sağlamıyor.

İşte katıldığım son bir büyük projeden gerçek bir örnek:

Uygulama, çok sık güncellenmeyen bir sunucu durumundan büyük miktarda veri kullanan birçok ayrı ekran ve bileşeni olan kalın bir istemciydi. Bu veriler temelde Singleton "manager" nesnesinde - korkunç "global durum" da önbellekte saklandı. Fikir, verileri depolanan ve senkronize eden uygulamada bir yere sahip olmaktı ve ardından açılan yeni ekranlar, sunucudan gelen çeşitli destekleyici veriler için tekrarlanan taleplerde bulunmaksızın, ihtiyaç duydukları şeyin çoğunu sorgulayabilir. Sunucuya sürekli istekte bulunmak çok fazla bant genişliği alırdı - ve haftada binlerce dolar fazladan İnternet faturasından söz ediyorum, bu kabul edilemezdi.

Temel olarak bu tür bir küresel veri yöneticisi önbellek nesnesine sahip olmaktan daha uygun başka bir yaklaşım var mı? Bu nesne resmen elbette "Singleton" olmak zorunda değildir, ancak kavramsal olarak bir anlam ifade eder. Burada güzel ve temiz bir alternatif nedir?


10
Çözülmesi gereken bir Singleton kullanımı ne problemdir? Bu problemi çözmede alternatiflerden (statik bir sınıf gibi) daha iyi ne olabilir?
Anon.

14
@Anon: Statik bir sınıf kullanmak durumu nasıl daha iyi hale getirir? Hala sıkı bağlantı var mı?
Martin York

5
@Martin: Bunu "daha iyi" yaptığını önermiyorum. Bir çok durumda, bir singleton'ın bir problem arayışı için bir çözüm olduğunu öneriyorum.
Anon.

9
@Anon: Doğru değil. Statik sınıflar size (neredeyse) örnekleme üzerinde hiçbir denetim sağlamaz ve çoklu iş parçacığını Singletons'dan daha da zorlaştırır (yalnızca örnek yerine her bir yönteme erişimi serileştirmeniz gerektiğinden). Singletons ayrıca en azından statik sınıfların yapamayacağı bir arabirim uygulayabilir. Statik sınıfların kesinlikle avantajları vardır, ancak bu durumda Singleton kesinlikle iki önemli kötünün altındadır. Herhangi bir değişken durumu uygulayan statik bir sınıf, yanıp sönen büyük bir neon gibidir "UYARI: KÖTÜ TASARIM! işaret.
Aaron,

7
@Aaronaught: Eğer singleton'a erişimi senkronize ediyorsanız , eşzamanlılığınız bozulur . Konu, singleton nesnesini getirdikten hemen sonra kesilebilir, başka bir konu başlar ve yarış durumu suçlanır. Statik bir sınıf yerine bir Singleton kullanmak, çoğu durumda, yalnızca uyarı işaretlerini ortadan kaldırmak ve sorunu çözeceğini düşünmektir .
Anon.

Yanıtlar:


809

Bu arasına Burada ayırt etmek önemlidir tek örnekleri ve Singleton tasarım deseni .

Tek örnekler basit bir gerçektir. Çoğu uygulama, bir seferde yalnızca bir yapılandırma, bir seferde bir kullanıcı arayüzü, bir seferde bir dosya sistemi vb. İle çalışmak üzere tasarlanmıştır. Korunacak çok fazla durum ya da veri varsa, o zaman kesinlikle sadece bir örneğe sahip olmak ve mümkün olduğunca uzun süre hayatta kalmasını istersiniz.

Singleton tasarım deseni çok özel bir tek örnek türüdür , özellikle aşağıdakilerden biridir:

  • Genel, statik örnek alanı aracılığıyla erişilebilir;
  • Program başlangıcında veya ilk erişime girildiğinde;
  • Hiçbir kamu kurucusu (doğrudan somutlanamaz);
  • Asla açıkça serbest bırakılmamalıdır (programın sona ermesinde dolaylı olarak serbest bırakılmıştır).

Bu özel tasarım seçiminden dolayı, desenin birkaç potansiyel uzun vadeli problemi ortaya çıkarmasından kaynaklanmaktadır:

  • Özet veya arayüz sınıfları kullanamama;
  • Alt sınıflamanın yetersizliği;
  • Uygulama boyunca yüksek bağlantı (değiştirilmesi zor);
  • Test etmek zor (birim testlerde sahte / alay edilemez);
  • Değişken hal durumunda paralelleştirmek zor (kapsamlı kilitleme gerektirir);
  • ve bunun gibi.

Bu belirtilerden hiçbiri aslında tek örneklere endemik değildir, sadece Singleton paterni.

Bunun yerine ne yapabilirsiniz? Singleton desenini kullanmayın.

Sorudan alıntı:

Fikir, verileri depolanan ve senkronize eden uygulamada bir yere sahip olmaktı ve ardından açılan yeni ekranlar, sunucudan gelen çeşitli destekleyici veriler için tekrarlanan taleplerde bulunmaksızın, ihtiyaç duydukları şeyin çoğunu sorgulayabilir. Sunucuya sürekli istekte bulunmak çok fazla bant genişliği alırdı - ve haftada binlerce dolar fazladan İnternet faturasından söz ediyorum, bu kabul edilemezdi.

Bu kavramın, bir nevi ipucu olarak, ancak belirsiz olduğu bir adı vardır. Buna önbellek denir . Fantezi edinmek istiyorsanız, buna "çevrimdışı önbellek" veya yalnızca uzaktaki verilerin çevrimdışı bir kopyası diyebilirsiniz.

Bir önbellek bir singleton olması gerekmez. Bu olabilir Birden önbellek örnekleri için aynı veriler alınırken kaçınmak istiyorsanız tek örneği olması gerekir; ama bu aslında her şeyi herkese açık tutmanız gerektiği anlamına gelmez .

Yapacağım ilk şey , önbelleğin farklı işlevsel alanlarını ayrı arayüzlere ayırmak. Örneğin, Microsoft Access'i temel alarak dünyanın en kötü YouTube klonunu yaptığınızı varsayalım:

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

Burada , belirli bir sınıfın erişmesi gerekebilecek belirli veri türlerini tanımlayan birçok arayüze sahipsiniz - medya, kullanıcı profilleri ve statik sayfalar (ön sayfa gibi). Bunların hepsi bir mega önbellek tarafından gerçekleştirilir , ancak bireysel sınıflarınızı bunun yerine arayüzleri kabul edecek şekilde tasarlarsınız, böylece ne tür bir örnek aldıklarını umursamazlar. Fiziksel örneği, programınız başladığında bir kez başlatırsınız ve ardından, örnekleri (belirli bir arabirim türüne gönderir) yapıcılar ve genel özellikler aracılığıyla aktarmaya başlarsınız.

Bu arada, Bağımlılık Enjeksiyonu olarak adlandırılır ; Spring veya herhangi bir özel IoC kabını kullanmanıza gerek yok; genel sınıf tasarımınız bağımlılıklarını kendi başına başlatmak veya global durumuna atıfta bulunmak yerine arayandan kabul ettiği sürece .

Arayüz tabanlı tasarımı neden kullanmalısınız? Üç sebep:

  1. Kodun okunmasını kolaylaştırır; Bağımlı sınıfların tam olarak hangi verilere bağlı olduğunu arabirimlerden net bir şekilde anlayabilirsiniz .

  2. Microsoft Access'in bir veri arka ucu için en iyi seçenek olmadığını fark ederseniz, bunu daha iyi bir şeyle değiştirebilirsiniz - SQL Server diyelim.

  3. SQL Server'ın özellikle medya için en iyi seçenek olmadığını fark ederseniz, uygulamanızı sistemin başka bir bölümünü etkilemeden kesebilirsiniz . Bu, soyutlamanın gerçek gücünün girdiği yerdir.

Bir adım daha ileri gitmek istiyorsanız, Spring (Java) veya Unity (.NET) gibi bir IoC kabı (DI framework) kullanabilirsiniz. Neredeyse her DI çerçevesi kendi yaşam boyu yönetimini yapacak ve belirli bir hizmeti tek bir örnek olarak tanımlamanıza izin verecektir (genellikle "singleton" olarak adlandırılır, ancak bu yalnızca tanıdıklık içindir). Temel olarak bu çerçeveler, örneklerin etrafından elle geçerek yapılan maymun çalışmasının çoğunu kurtarır, ancak kesinlikle gerekli değildir. Bu tasarımı uygulamak için herhangi bir özel araca ihtiyacınız yoktur.

Tamlık uğruna, yukarıdaki tasarımın gerçekten de ideal olmadığını belirtmeliyim. Önbellekle uğraşırken (olduğu gibi), aslında tamamen ayrı bir katmana sahip olmanız gerekir . Başka bir deyişle, bunun gibi bir tasarım:

                                                        + - IMediaRepository
                                                        |
                          Önbellek (Genel) --------------- + - IProfilRepository
                                ▲ |
                                | + - IPageRepository
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

Bunun yararı, Cacheyeniden ateşlemeye karar verirseniz , örneğinizi asla kesmenize gerek kalmamasıdır; Medya'nın saklanma şeklini, alternatif bir uygulamasını besleyerek değiştirebilirsiniz IMediaRepository. Bunun nasıl bir araya geldiğini düşünürseniz, bunun yalnızca bir fiziksel önbellek örneği oluşturduğunu göreceksiniz, bu yüzden asla aynı verileri iki kez almak zorunda kalmayacaksınız.

Bunların hiçbiri, dünyadaki her bir yazılım parçasının, bu yüksek uyum ve gevşek bağlantı standartlarına uyması gerektiğini söylemek değildir; Bu, projenin boyutuna ve kapsamına, ekibinize, bütçenize, son teslim tarihlerine vb. bağlıdır.

Not: Diğerlerinin de belirttiği gibi, bağımlı sınıfların bir önbellek kullandıklarının farkında olmaları muhtemelen en iyi fikir değildir - bu, kesinlikle umursamayacakları bir uygulama detayıdır. Olduğu söyleniyor, genel mimari hala yukarıda gösterilene çok benzeyecek, bireysel arayüzlere önbellek olarak bakmayacaktınız . Bunun yerine, onlara Hizmetler veya benzeri bir ad verin .


131
İlk yazımı, DI'yi gerçekten küresel duruma alternatif olarak açıklayan okudum. Buna harcadığınız zaman ve emeğiniz için teşekkür ederiz. Bu yazının sonucu olarak hepimiz daha iyiyiz.
MrLane

4
Önbellek neden singleton olamıyor? Etrafı dolaştırıp bağımlılık enjeksiyonu kullanıyorsanız, bu bir singleton değil mi? Singleton kendimizi bir örnekle sınırlandırmakla ilgilidir, nasıl erişildiği değil mi? Bu konuyla
Erik Engheim 17:13

29
@AdamSmith: Bu cevabın herhangi birini okudunuz mu? Sorunuz ilk iki paragrafta cevaplandırılmıştır. Singleton Pattern! == Tek Örnek.
Aaron,

5
@Cawas ve Adam Smith - Bağlantılarınızı okuyarak bu cevabı gerçekten okumadığınız hissine kapılıyorum - her şey zaten orada.
Wilbert

19
@Bu cevabın etinin, tek örnekli ve singleton arasındaki ayrım olduğunu hissediyorum. Singleton kötüdür, tek seferlik değildir. Bağımlılık Enjeksiyonu, singletons kullanmak zorunda kalmadan tek örnekleri kullanmanın güzel ve genel bir yoludur.
Wilbert

48

Verdiğiniz durumda, bir Singleton kullanımı sorun değil, bir sorunun belirtisi - daha büyük, mimari bir sorun gibi görünüyor.

Ekranlar neden önbellek nesnesini veriler için sorguluyor? Önbellekleme müşteriye şeffaf olmalıdır. Verileri sağlamak için uygun bir soyutlama yapılmalı ve bu soyutlamanın uygulanmasında önbellekleme kullanılabilir.

Mesele, sistemin bölümleri arasındaki bağımlılıkların doğru kurulmamış olması ve bu muhtemelen sistemik.

Ekranların neden verilerini nereden aldıklarını bilmesi gerekiyor? Ekranlar neden veri isteklerini yerine getirebilecek bir nesneye sahip değiller? Çoğu zaman ekran oluşturma sorumluluğu merkezileştirilmez ve bu nedenle bağımlılıkları enjekte etmenin açık bir noktası yoktur.

Yine, büyük ölçekli mimari ve tasarım konularına bakıyoruz.

Ayrıca, bir nesnenin kullanım ömrünün , nesnenin kullanımda bulunma şeklinden tamamen boşaltılabileceğini anlamak çok önemlidir .

Bir önbellek uygulamanın ömrü boyunca (faydalı olması için) yaşamak zorunda kalacak, böylece nesnenin ömrü bir Singleton'a ait olacak.

Ancak, Singleton'daki sorun (en azından Singleton'un statik bir sınıf / özellik olarak yaygın bir şekilde uygulanması), onu kullanan diğer sınıfların onu bulma yolunda gitmesidir.

Statik bir Singleton uygulamasıyla, sözleşme sadece gereken her yerde kullanmaktır. Ancak bu bağımlılığı tamamen gizler ve iki sınıfı sıkı bir şekilde birleştirir.

Biz ise temin sınıfa bağımlılığı, yani bağımlılık açık ve tüm tüketim sınıfı kullanımı için uygun sözleşme bilgisine sahip olması gerekir.


2
Bazı ekranların ihtiyaç duyabileceği, ancak mutlaka gerekmesi gerekmeyen çok miktarda veri vardır. Ve bunu tanımlayan kullanıcı eylemleri gerçekleştirilinceye kadar bilmiyorsunuz - ve çok, birçok kombinasyon var. Böylece, müşteride önbellekte tutulan ve eşitlenen (çoğunlukla giriş sırasında elde edilen) bazı genel global verilere sahip olmak ve daha sonra talepler önbelleği daha fazla oluşturur, çünkü açıkça talep edilen veriler yeniden kullanılmaya meyillidir. Aynı seans. Odak sunucuya istekte bulunma, bu nedenle bir istemci tarafı önbelleğe ihtiyaç duyulmasının azaltılması. <cont>
Bobby,

1
<cont> Esasen şeffaftır. Bazı gerekli veriler henüz önbelleğe alınmamışsa, sunucudan bir geri arama olduğu anlamına gelir. Ancak bu önbellek yöneticisinin uygulaması (mantıksal ve fiziksel olarak) bir Singleton'dur.
Bobby Masalar

6
Burada qstarin ile birlikteyim: Verilere erişen nesnelerin verilerin önbelleğe alındığını (bir uygulama detayı olduğunu) bilmemeli (veya bilmesi gerekir). Verinin kullanıcıları sadece veri ister (veya veriyi almak için bir arayüz ister).
Martin York

1
Önbellekleme aslında bir uygulama detayıdır. Verilerin sorgulandığı bir arabirim var ve bunları alan nesneler önbellekten gelip gelmediğini bilmiyor. Ancak bu önbellek yöneticisinin altında bir Singleton var.
Bobby Tables,

2
@ Bobby Tablolar: o zaman durumunuz göründüğü kadar cüretkar değil. Bu Singleton (statik bir sınıf demek istediğinizi varsayarsak, yalnızca uygulamanın uzun süren bir örneği olan bir nesneyi değil) hala sorunludur. Veri sağlayan nesnenizin bir önbellek sağlayıcısına bağımlı olduğu gerçeğini gizliyor. Açık ve dışsallaştırılmışsa daha iyidir. Onları ayır. Öyle esansiyel kolayca bileşenleri yerini alabilir test edilebilir ve bir önbellek sağlayıcı bir olan asal böyle bir bileşenin örneği (ASP.Net tarafından desteklenen bir önbellek sağlayıcısıdır ne sıklıkta).
quentin-starin

45

Sadece bu soru üzerine bir bölüm yazdım . Çoğunlukla oyunlar bağlamında, fakat bunların çoğu oyunların dışında uygulanmalıdır.

tl; dr:

Four Singleton deseninin çetesi iki şeyi yapar: nesneye herhangi bir yerden rahatça erişmenizi sağlar ve bunun yalnızca bir örneğinin oluşturulabildiğinden emin olun. Zamanınızın% 99'u, tek umursadığınız şeyin ilk yarısı ve ikinci yarısı boyunca arabaya binmek gereksiz bir sınırlama getiriyor.

Sadece bu değil, aynı zamanda uygun erişim sağlamak için daha iyi çözümler var. Bir nesneyi küresel yapmak, bunu çözmek için nükleer bir seçenektir ve enkapsülasyonunuzu imha etmeyi kolaylaştırır. Globals hakkında kötü her şey tamamen singleton için geçerlidir.

Kodda aynı nesneye dokunması gereken birçok yeriniz olduğu için kullanıyorsanız, kod nesnesinin tamamını göstermeden yalnızca bu nesnelere vermenin daha iyi bir yolunu bulun . Diğer çözümler:

  • Tamamen hendek kazın. Herhangi bir durumu olmayan ve sadece yardımcı fonksiyonların çantaları olan birçok singleton sınıfı gördüm. Bunların bir örneğine ihtiyacı yok. Sadece onlara statik fonksiyonlar yapın veya onları fonksiyonun argüman olarak aldığı sınıflardan birine taşıyın. Yapabilseydin özel bir Mathsınıfa ihtiyacın olmazdı 123.Abs().

  • Etrafından geç. Bir yöntemin başka bir nesneye ihtiyaç duyması durumunda basit çözüm, sadece içeri aktarmaktır. Bazı nesnelerin etrafından geçirilmesinde yanlış bir şey yoktur.

  • Temel sınıfa koyun. Bazı özel nesnelere erişmesi gereken çok sayıda sınıfınız varsa ve bir temel sınıfı paylaşırlarsa, bu nesneyi taban üzerinde üye yapabilirsiniz. İnşa ederken, nesneyi iletin. Artık türetilmiş nesneler ihtiyaç duyduklarında onu alabilir. Korumalı yaparsanız, nesnenin hala kapalı kaldığından emin olursunuz.


1
Burada özetler misiniz?
Nicole

1
Tamam, ama hala tüm bölümü okumanı tavsiye ediyorum.
haziran

4
başka bir alternatif: Bağımlılık Enjeksiyonu!
Brad Cupit

1
@BradCupit de bu linkte onun hakkında konuşuyor ... Ben hala hepsini sindirmeye çalışıyorum söylemeliyim. Ama şimdiye kadar okuduğum singletonlarda en aydınlatıcı okuma oldu. Şimdiye kadar, tıpkı global değişkenler gibi, tekillere ihtiyaç duyduğum için pozitifdi ve Araç Kutusu'nu tanıtıyordum . Şimdi artık bilmiyorum. Bay munificient , servis bulucunun sadece statik bir araç kutusu olup olmadığını söyleyebilir misiniz ? Bunu bir singleton (yani bir alet çantası) yapmak daha iyi olmaz mıydı?
cregox

1
"Araç Kutusu" bana "Servis Yer Belirleme" ye oldukça benziyor. Bunun için bir statik kullanıyorsanız veya tek bir tekil olun, bence çoğu program için önemli değil. Statiğe dayanma eğilimindeyim çünkü mecbur değilseniz neden tembel başlatma ve yığın tahsisi ile uğraşıyorsunuz?
belediye

21

Sorun şu ki küresel durum değil.

Gerçekten sadece endişelenmen gerekiyor global mutable state. Sabit durum yan etkilerden etkilenmez ve bu nedenle daha az problem yaratır.

Singleton ile ilgili en büyük endişe, kuplaj eklemesi ve bu nedenle test etmeyi zorlaştırmasıdır. Singleton'u başka bir kaynaktan (örneğin bir fabrikadan) alarak kaplini azaltabilirsiniz. Bu, kodu belirli bir örnekten ayırmanıza izin verir (fabrikaya daha çok bağlanmanıza rağmen (ancak en azından fabrikanın farklı aşamalar için alternatif uygulamaları olabilir)).

Sizin durumunuzda, singleton'unuz aslında bir arayüz uyguladığı sürece bundan kurtulabileceğinizi düşünüyorum (bir alternatif başka durumlarda da kullanılabilir).

Ancak, tekillerle olan diğer bir büyük sorun ise, bir kez yerine koyduklarında, koddan çıkarmaları ve başka bir şeyle değiştirilmelerinin gerçek bir zor iş haline gelmesidir (yine bu birleşme söz konusudur).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

Bu mantıklı. Bu aynı zamanda, Singletons'u asla gerçekten kötüye kullanmadığımı, ancak onları HERHANGİ bir kullanımdan şüphe etmeye başladığımı düşündürüyor. Ancak, bu noktalara göre yaptığım herhangi bir dürüst tacizi düşünemiyorum. :)
Bobby Tablolar,

2
(muhtemelen demek başına değil "demek başına")
nohat

4
@nohat: Ben "Queens English" in ana konuşmacısıyım ve bu yüzden daha iyisini yapmadıkça Fransızca olan herhangi bir şeyi reddediyoruz le weekend(bizden biri olan ayetler gibi). Thanks :-)
Martin York

21
per latin'dir.
Anon.

2
@Anon: Tamam. O zaman o kadar da kötü değil ;-)
Martin York

19

Sonra ne? Kimse söylemedi çünkü: Araç Kutusu . Global değişkenler istiyorsanız budur .

Tek bir suistimal , soruna farklı bir açıdan bakılarak önlenebilir. Bir uygulamanın bir sınıfın yalnızca bir örneğine ihtiyacı olduğunu ve uygulamanın başlangıçta o sınıfı yapılandırdığını varsayalım: Sınıfın neden bir singleton olmaktan sorumlu olması gerekir? Uygulamanın bu tür bir davranış gerektirdiği için uygulamanın bu sorumluluğu üstlenmesi mantıklı görünüyor. Bileşen değil, uygulama tekil olmalıdır. Ardından uygulama, uygulamaya özgü herhangi bir kodun kullanması için bileşenin bir örneğini sağlar. Bir uygulama bu gibi birkaç bileşen kullandığında, onları bir araç kutusu olarak adlandırdıklarımızda toplayabilir.

Basitçe söylemek gerekirse, uygulamanın araç kutusu, kendisini yapılandırmaktan veya uygulamanın başlatma mekanizmasının onu yapılandırmasına izin vermekten sorumlu olan tek bir ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Ama tahmin et ne oldu? Bu bir singleton!

Ve bir singleton nedir?

Belki de karışıklığın başladığı yer orasıdır.

Bana göre, singleton sadece ve her zaman tek bir örneğe sahip olmaya zorlanan bir nesnedir. Bir örneğe, herhangi bir zamanda, başlatmanıza gerek kalmadan erişebilirsiniz. Bu yüzden bununla yakından alakalı static. Karşılaştırma için, statictemelde aynı, bir örnek değil. Bunu somutlaştırmamız gerekmiyor, hatta yapamıyoruz, çünkü otomatik olarak ayrılmış. Ve bu sorun yaratabilir ve getirir.

Tecrübelerime göre, sadece staticSingleton ile değiştirmek , üzerinde bulunduğum orta büyüklükteki patchwork çanta projesinde birçok sorunu çözdü. Bu sadece kötü tasarlanmış projeler için bazı kullanım olduğu anlamına gelir. Çok fazla olduğunu düşünüyorum tartışma ise tekil desen olduğunu kullanışlı olmadığını ve gerçekten eğer gerçekten iddia edemez kötü . Fakat yine de , genel olarak, statik metotlar üzerindeki singleton lehine iyi argümanlar var .

Emin olduğum tek şey singletonlar için kötü, onları iyi uygulamaları görmezden gelirken kullanıyoruz. Bu gerçekten başa çıkmak kolay değil bir şey. Ancak kötü uygulamalar herhangi bir kalıba uygulanabilir. Ve biliyorum ki, bunu söylemek çok genel ... Demek istediğim çok fazla şey var.

Beni yanlış anlama!

Basitçe söylemek gerekirse, tıpkı küresel vars , singletons gerektiğini hala her zaman kaçınılmalıdır . Özel olarak aşırı suistimal edildiğinden. Ancak küresel değişkenler her zaman önlenemez ve onları son durumda kullanmalıyız.

Her neyse, Araç Kutusu'ndan başka birçok öneri var ve araç kutusu gibi, her birinin kendi uygulaması var ...

Diğer alternatifler

  • Sadece singletons hakkında okuduğum en iyi makale anlaşılacağı Servis Locator alternatif olarak. Bana göre bu temelde bir " Statik Araç Kutusu ". Başka bir deyişle, Servis Bulucu’yu Singleton yapın ve bir Toolbox'ınız var. Tabii ki, tekil olmaktan kaçınmak ilk önerisine ters düşüyor, elbette, ancak bu sadece tekil meseleyi zorlamak için, kendi içindeki kalıbı değil, nasıl kullanıldığıdır.

  • Diğerleri ise Fabrika Desenini alternatif olarak önerir . Bir meslektaşımdan duyduğum ilk alternatifti ve küresel var olarak kullanımımız için hızlıca ortadan kaldırdık . Tabii ki kullanımı var, ancak singletonlar da öyle.

Yukarıdaki her iki alternatif de iyi alternatiflerdir. Ancak bunların tümü kullanımınıza bağlıdır.

Şimdi, tekilleşmeleri ima etmek, her ne pahasına olursa olsun kaçınılması gereken ...

  • Aaronaught'ın cevabı, bir dizi nedenden dolayı asla singleton kullanmamaya işaret ediyor . Fakat bunların hepsi, nasıl kullanıldıklarına ve kötüye kullanılmalarına karşı, doğrudan desene karşı değil. Bu konulardaki endişelere katılıyorum, nasıl yapamam? Sadece yanıltıcı olduğunu düşünüyorum.

Yetersizlikler (soyut veya alt sınıfa) gerçekten orada, ama ne olmuş yani? Bunun için değil. Söyleyebileceğim kadarıyla, arayüz arayüzeyinde yetersizlik yok . Yüksek kavrama da orada olabilir, ancak bunun nedeni yaygın olarak kullanılmasıdır. Zorunda değil . Aslında, kuplajın kendi içinde singleton deseniyle ilgisi yoktur. Bu açıklığa kavuşturulması, zaten test etmek zorluğunu da ortadan kaldırıyor. Paralelleştirme zorluğuna gelince, bu, dile ve platforma bağlıdır, bu nedenle, yine model üzerinde bir sorun olmaz.

Pratik örnekler

Sık sık hem lehine hem de tekillere karşı kullanıldığını görüyorum. Web önbelleği (benim durumum) ve günlük servisi .

Bazıları, günlüklerin mükemmel bir singleton örneği olduğunu iddia ediyor , çünkü şunu alıntı yapıyorum:

  • İstek sahiplerinin, günlüğe kaydedilecek istek gönderecekleri iyi bilinen bir nesneye ihtiyaçları vardır. Bu, küresel bir erişim noktası anlamına gelir.
  • Kayıt servisi, birden fazla dinleyicinin kayıt olabileceği tek bir olay kaynağı olduğundan, yalnızca bir örnek olması gerekir.
  • Her ne kadar farklı uygulamalar farklı çıktı cihazlarına giriş yapabilseler de, dinleyicilerini kaydetme biçimleri her zaman aynıdır. Tüm kişiselleştirme dinleyiciler aracılığıyla yapılır. Müşteriler, metnin nasıl veya nereye kaydedileceğini bilmeden günlük kaydı isteyebilir. Bu nedenle her uygulama kayıt servisini aynı şekilde kullanır.
  • Herhangi bir uygulama günlük hizmetinin sadece bir örneği ile kurtulabilmelidir.
  • Herhangi bir nesne, yeniden kullanılabilir bileşenler de dahil olmak üzere bir kayıt isteği olabilir, böylece belirli bir uygulamaya birleştirilmemelidir.

Diğerleri tartışacak olsa da, nihayetinde bunun tek bir örnek olmaması gerektiğini anladıktan sonra günlük hizmetini genişletmeyi zorlaştırıyor.

Her iki argümanın da geçerli olduğunu söylüyorum. Burada sorun, yine, singleton modelinde değil. Mimari kararlar ve yeniden yapılanmanın uygulanabilir bir risk olup olmadığını vurgulamak. Genellikle refactoring en son ihtiyaç duyulan düzeltici önlem olduğunda başka bir problemdir.


@gnat Teşekkürler! Singletons kullanma hakkında bazı uyarılar eklemek için cevabı düzenlemeyi düşünüyordum ... Alıntı mükemmel uyuyor!
cregox

2
Beğendiğine sevindim. Bu downvotes önlemek için yardımcı olacaktır emin değilim gerçi - muhtemelen okuyucuların zor anlar özellikle ışığında, söz konusu düzenlendiği beton sorununa bu yazı yapılan noktasına bağlayan yaşıyorsanız müthiş analizler önceki cevabı sağlanan
sivrisineği

@gnat evet, bunun uzun bir savaş olduğunu biliyordum. Umarım zaman söyler. ;-)
cregox

1
Buradaki genel yönünüze katılıyorum, ancak aşırı derecede kemiksiz bir IoC kabından (örneğin, Funq'tan daha basit değil) çok fazla görünmeyen bir kütüphane konusunda biraz fazla hevesli olmasına rağmen . Servis Bulucu aslında bir anti-kalıptır; temelde eski / brownfield projelerinde ve “yoksul adamın DI'si” ile birlikte, bir IoC konteynerini düzgün kullanmak için her şeyi yeniden yansıtmanın çok pahalı olacağı yararlı bir araçtır.
Aaron,

1
@Cawas: Ah, üzgünüm - kafam karıştı java.awt.Toolkit. Benim düşüncem aynı: Toolboxtek bir amaca uygun bir sınıftan ziyade, ilgisiz bit ve parçalardan oluşan bir kepçe çantası gibi geliyor. Bana iyi bir tasarım gibi gelmiyor. (Bahsettiğiniz makalenin, bağımlılık enjeksiyonu ve DI kapları yaygınlaşmadan önce 2001 yılına ait olduğunu unutmayın.)
Jon Skeet

5

Singleton tasarım modelindeki temel sorunum, uygulamanız için iyi birim testleri yazmanın çok zor olmasıdır.

Bu "yöneticiye" bağımlılığı olan her bileşen bunu singleton örneğini sorgulayarak yapar. Ve eğer böyle bir bileşen için bir birim testi yazmak istiyorsanız, bu singleton örneğine veri enjekte etmeniz gerekir ki bu kolay olmayabilir.

Öte yandan, "yöneticiniz" bağımlı bileşenlere bir constructor parametresiyle enjekte edilirse ve bileşen yöneticinin somut türünü bilmiyorsa, yalnızca yöneticinin uyguladığı bir arabirim veya soyut temel sınıfı, ardından bir birim Test bağımlılıkları test ederken yöneticinin alternatif uygulamalarını sağlayabilir.

Uygulamanızı oluşturan bileşenleri yapılandırmak ve somutlaştırmak için IOC kapsayıcılarını kullanıyorsanız, IOC kapsayıcınızı "yöneticinin" yalnızca bir örneğini oluşturacak şekilde kolayca yapılandırabilir ve aynı, genel uygulama önbelleğini kontrol eden aynı örneği elde etmenize izin verebilirsiniz .

Ancak, birim testlerini umursamıyorsanız, bir singleton tasarım deseni tamamen iyi olabilir. (ama yine de yapmazdım)


Güzel anlatılmış, bu Singletons test ile ilgili en iyi açıklayan cevap budur
José Tomás Tocino

4

Bir tekil temel bir şekilde değil kötü şey tasarım bilgisayar iyi veya kötü olabilir anlamda. Sadece doğru olabilir (beklenen sonuçları verir) ya da olmayabilir. Ayrıca kodu daha net veya daha verimli hale getirirse, yararlı olabilir veya olmayabilir.

Singleton'ların faydalı olduğu bir durum, gerçekten benzersiz olan bir varlığı temsil ettikleri zamandır. Çoğu ortamda, veritabanları benzersizdir, gerçekten yalnızca bir veritabanı vardır. Bu veritabanına bağlanmak, özel izinler gerektirdiğinden veya birkaç bağlantı türünden geçtiği için karmaşık olabilir. Bu bağlantıyı bir tekil tonda düzenlemek, muhtemelen bu sebep için çok anlamlı.

Ancak, singleton'un gerçekten bir singleton olduğundan ve global bir değişken olmadığından emin olmanız gerekir. Bu, tek, benzersiz bir veritabanının üretim, evreleme, geliştirme ve test fikstürleri için her biri birer tane olmak üzere 4 veritabanı olması durumunda önemlidir. Bir veritabanı Singleton, hangisine bağlanmak istediğini belirler, o veritabanı için tek bir örneği alır, gerekirse bağlar ve arayana geri verir.

Bir singleton gerçekten bir singleton olmadığında (çoğu programcının üzülmesine neden olur), tembel bir şekilde küreselleşmiş bir dünyadır, doğru bir örnek enjekte etme imkanı yoktur.

İyi tasarlanmış bir singleton modelinin bir başka kullanışlı özelliği, genellikle gözlenememesidir. Arayan bir bağlantı ister. Sağlayan hizmet, havuzlanmış bir nesneyi geri döndürebilir veya bir test gerçekleştiriyorsa, her arayan için yeni bir tane oluşturabilir veya bunun yerine sahte bir nesne sağlayabilir.


3

Gerçek nesneleri temsil eden singleton deseninin kullanılması tamamen kabul edilebilir. IPhone için yazıyorum ve Cocoa Touch çerçevesinde birçok singleton var. Uygulamanın kendisi, sınıfın bir tekilliği ile temsil edilir UIApplication. Tek bir uygulama var, o yüzden bunu bir singleton ile temsil etmek uygun.

Singleton'u veri yöneticisi sınıfı olarak kullanmak, doğru tasarlandığı sürece tamamdır. Bu bir veri özellikleri kovasıysa, küresel kapsamdan daha iyi değildir. Bir dizi alıcı ve ayarlayıcı varsa, bu daha iyi, ama yine de harika değil. Belki de uzaktaki verileri almak, önbelleğe almak, kurulum ve teardown dahil tüm veri arayüzünü gerçekten yöneten bir sınıfsa ... Bu çok faydalı olabilir.


2

Singletons, servis odaklı bir mimarinin sadece bir programa yansımasıdır.

Bir API, protokol seviyesindeki bir singleton örneğidir. Twitter, Google vb. Aslında tekil olanları kullanarak erişirsiniz. Peki, neden bir program içinde singletonlar kötüleşiyor?

Bir program hakkında ne düşündüğünüze bağlı. Eğer bir programı rastgele bağlı önbellek örnekleri yerine hizmet topluluğu olarak görüyorsanız, o zaman singletons mükemmel bir anlam ifade eder.

Singletons bir servis erişim noktasıdır. Genel olarak, çok karmaşık bir iç mimariyi gizleyen, sıkı sıkıya bağlı bir işlevsellik kütüphanesine arayüz.

Bu yüzden bir fabrikadan farklı bir singleton görmüyorum. Singleton, yapıcı parametrelerinin geçmesine neden olabilir. Örneğin, varsayılan yazıcının olası tüm seçim mekanizmalarına karşı nasıl çözüleceğini bilen bir bağlam tarafından yaratılabilir. Test için kendi sahte cihazınızı ekleyebilirsiniz. Bu yüzden oldukça esnek olabilir.

Anahtar, dahili olarak bir programda yürütürüm ve biraz işlevselliğe ihtiyaç duyduğumda, hizmetin hazır ve kullanıma hazır olduğuna tamamen güvenerek singleton'a erişebilirim. Bu, hazır olması için bir durum makinesinden geçmesi gereken bir işlemde başlayan farklı dişler olduğunda anahtardır.

Tipik olarak XxxService, bir singletonu sınıfın etrafına saracak bir sınıfı saracağım Xxx. Singleton hiç sınıfta değil, Xxxbaşka bir sınıfa ayrılmış XxxService,. Bunun nedeni Xxx, muhtemel olmasa da, birden fazla örneğe sahip olabilir, ancak yine Xxxde her sistemde küresel olarak erişilebilir bir örneğe sahip olmak istiyoruz . XxxServicekaygıların güzel bir şekilde ayrılmasını sağlar. XxxBir singleton politikasını uygulamak zorunda değil, ancak Xxxgerektiğinde singleton olarak kullanabiliriz .

Gibi bir şey:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

İlk soru, uygulamada bir sürü hata buluyor musunuz? belki önbelleği veya kötü önbelleği güncellemeyi veya değiştirmeyi zor bulmayı unutmayı? (Bir uygulamanın siz de rengini değiştirmediyseniz boyutları değiştirmeyeceğini hatırlıyorum ... ancak rengini geri değiştirebilir ve boyutunu koruyabilirsiniz).

Yapacağınız şey bu sınıfa sahip olmak, ancak TÜM STATİK ÜYELERİ ÇIKARMAK. Tamam bu gerekli değil ama bunu tavsiye ederim. Gerçekten sadece sınıfı normal bir sınıf gibi başlatırsınız ve işaretçiyi PASS olarak koyarsınız.

Daha fazla iş ama gerçekten, daha az kafa karıştırıcı. Artık bir şeyleri değiştirmemeniz gereken yerler, artık küresel olmadığı için yapamazsınız. Tüm menajer derslerim normal derslerdir, sadece öyle davranın.


1

IMO, örneğiniz iyi geliyor. Şu şekilde faktoring yapmayı öneriyorum: her (ve arkasındaki) veri nesnesi için önbellek nesnesi; önbellek nesneleri ve db erişimci nesneleri aynı arabirime sahiptir. Bu, önbellekleri kodun içinde ve dışında değiştirme olanağı sağlar; Ayrıca kolay bir genişleme rotası sağlar.

Grafik:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

DB erişimcisi ve önbellek, aynı nesne ya da ördek tipinden aynı nesne gibi görünmek için miras alabilir. Tak / derle / test edebildiğiniz sürece ve hala çalışıyorsa.

Bu, işleri ayrıştırır, böylece içeri girip Uber-Cache nesnesini değiştirmeden yeni önbellekler ekleyebilirsin. YMMV. IANAL. VB.


1

Partiye biraz geç kaldım ama yine de.

Singleton, bir araç kutusundaki bir araçtır, her şey gibi. Umarım, alet çantanda sadece tek bir çekiçten daha fazlasına sahipsindir.

Bunu düşün:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

vs

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

1. durum, yüksek kavrama vb. Yol açar; 2. yolun hiçbir sorunu yok @ Aaronaught anlattığım kadarıyla anlatıyor. Her şey onu nasıl kullandığınızla ilgili.


Aynı fikirde olmamak, anlaşamamak. İkinci yol “daha ​​iyi” olmasına rağmen (azaltılmış eşleşme), DI tarafından çözülmesi gereken sorunlar var. Hizmetlerin uygulanmasını sağlamak için sınıfınızın tüketicisine güvenmemelisiniz - bu, sınıf oluşturulduğunda yapıcıda daha iyidir. Arabiriminiz yalnızca bağımsız değişkenler açısından minimumda kalmalıdır. Ayrıca, sınıfınızın devam etmesi için tek bir örneğe ihtiyaç duyması için makul bir şans vardır - bu kuralı uygulamak için tüketiciye güvenmek riskli ve gereksizdir.
AlexFoxGill

Bazen Di, verilen bir iş için fazladan bir sorumluluktur. Bir örnek koddaki yöntem, bir kurucu olabilir, ancak zorunlu değil - somut örneğe bakmadan, tartışmanın bir argümanıdır. Ayrıca, DoSomething bir şeyler alabilir ve MySingleton bu arayüzü uyguluyor olabilir - tamam, bu bir örnekte değil .. ama onun bir örneği.
Evgeni

1

Her ekranın kendi yapıcısında Yöneticiyi ele geçirmesini sağlayın.

Uygulamanıza başladığınızda, yöneticinin bir örneğini oluşturup etrafa iletirsiniz.

Buna Inversion of Control denir ve yapılandırma değiştiğinde ve testlerde denetleyiciyi değiştirmenize olanak sağlar. Ek olarak, uygulamanızın birkaç örneğini veya uygulamanızın bölümlerini paralel olarak çalıştırabilirsiniz (test için iyi!). Son olarak, yöneticiniz kendi nesnesiyle (başlangıç ​​sınıfı) ölecektir.

Uygulamanızı bir ağaç gibi yapılandırın; yukarıdaki şeyler, her şeyin altında kullanılan her şeye sahiptir. Herkesin herkesi tanıdığı ve birbirini global yöntemlerle bulduğu ağ gibi bir uygulamayı uygulama.

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.