Singleton'lar kötüyse, neden bir Servis Konteyneri iyidir?


91

Tekillerin ne kadar kötü olduğunu hepimiz biliyoruz çünkü bağımlılıkları gizlerler ve başka nedenlerle .

Ancak bir çerçevede, yalnızca bir kez somutlaştırılması ve her yerden çağrılması gereken birçok nesne olabilir (logger, db vb.).

Bu sorunu çözmek için bana Servislere yapılan her referansı (kaydedici vb.) Dahili olarak depolayan "Nesne Yöneticisi" (veya symfony gibi Servis Konteyneri ) kullanmam söylendi .

Ama bir Servis Sağlayıcı neden saf bir Singleton kadar kötü değil?

Servis sağlayıcı da bağımlılıkları gizler ve sadece birinci mevkinin yaratılışını tamamlar. Bu yüzden neden tekil yerine bir servis sağlayıcı kullanmamız gerektiğini anlamakta zorlanıyorum.

PS. Bağımlılıkları gizlememek için DI kullanmam gerektiğini biliyorum (Misko'nun belirttiği gibi)

Ekle

Ekleyeceğim: Bu günlerde tekil sesler o kadar kötü değil, PHPUnit'in yaratıcısı burada açıkladı:

DI + Singleton sorunu çözer:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

bu her sorunu çözmese bile oldukça akıllıca.

DI ve Hizmet Konteyneri dışında bu yardımcı nesnelere erişmek için kabul edilebilir herhangi bir çözüm var mı?


2
@yes Düzenlemeniz yanlış varsayımlar yapıyor. Sebastian, hiçbir şekilde, kod parçacığının Singleon'ları daha az sorun haline getirdiğini önermiyor. Aksi takdirde daha fazla test edilebilirliği test etmek imkansız olacak kod yapmanın sadece bir yoludur. Ama yine de sorunlu bir kod. Aslında, açıkça şunu belirtiyor: "Sadece Yapabildiğin İçin, Yapman gerektiği anlamına gelmez". Doğru çözüm yine de Singleton kullanmamak olacaktır.
Gordon

3
@yes SOLID ilkesini takip ediyor.
Gordon

19
Tekillerin kötü olduğu iddiasına itiraz ediyorum. Kötüye kullanılabilirler, evet ama herhangi bir araç da kullanabilir. Bir neşter, bir hayat kurtarmak veya onu bitirmek için kullanılabilir. Elektrikli testere, orman yangınlarını önlemek için ormanları temizleyebilir veya ne yaptığınızı bilmiyorsanız kolunuzun büyük bir bölümünü kesebilir. Akıllıca araçlarını kullanmak ve öğrenin değil bu şekilde yalanlarla düşünmeyen aklı - gospel gibi tedavi tavsiye.
paxdiablo

4
@paxdiablo ama olan kötü. Singletonlar SRP, OCP ve DIP'yi ihlal eder. Uygulamanıza küresel durum ve sıkı bağlantı sunarlar ve API'nizin bağımlılıkları hakkında yalan söylemesini sağlarlar. Bütün bunlar kodunuzun sürdürülebilirliğini, okunabilirliğini ve test edilebilirliğini olumsuz yönde etkileyecektir. Bu dezavantajların küçük faydalara ağır bastığı ender durumlar olabilir, ancak% 99'da bir Singleton'a ihtiyacınız olmadığını iddia ediyorum. Özellikle, Singleton'ların yalnızca İstek için benzersiz olduğu ve bir Builder'dan ortak çalışan grafikleri bir araya getirmenin çok basit olduğu PHP'de.
Gordon

5
Hayır, inanmıyorum. Bir araç, bir işlevi, genellikle bir şekilde daha kolay hale getirerek gerçekleştirmenin bir yoludur, ancak bazılarının (emacs?) Onu zorlaştırma gibi nadir bir ayrımı vardır :-) Bunda, bir tekil, dengeli bir ağaçtan veya bir derleyiciden farklı değildir. . Bir nesnenin yalnızca bir kopyasını sağlamanız gerekiyorsa, bunu bir tekil yapar. O öyle olsun iyi tartışılabilir ama bunu hiç yapmaz iddia inanmıyorum. Elektrikli testerenin el testeresinden daha hızlı olması veya çivi tabancasına karşı çekiç gibi daha iyi yollar olabilir. Bu el testeresini / çekiçleri daha az alet yapmaz.
paxdiablo

Yanıtlar:


76

Servis Bulucu, tabiri caizse iki kötülükten daha azıdır. Bu dört farklılığa indirgenen "daha az" ( en azından şu anda başkalarını düşünemiyorum ):

Tek Sorumluluk İlkesi

Hizmet Konteyneri, Singleton'ın yaptığı gibi Tek Sorumluluk İlkesini ihlal etmez. Singletons, nesne oluşturma ile iş mantığını karıştırırken, Hizmet Konteyneri, uygulamanızın nesne yaşam döngülerini yönetmekten kesinlikle sorumludur. Bu bakımdan Servis Konteyneri daha iyidir.

Kaplin

Statik yöntem çağrıları nedeniyle genellikle uygulamanıza tekil kodlar eklenir, bu da kodunuzda sıkı bağlı ve alay edilmesi zor bağımlılıklara yol açar . Öte yandan SL sadece bir sınıftır ve enjekte edilebilir. Yani, tüm sınıflandırmalarınız buna bağlı olsa da, en azından gevşek bağlı bir bağımlılıktır. Dolayısıyla, ServiceLocator'ı tek başına bir Singleton olarak uygulamadıysanız, bu biraz daha iyi ve test etmesi daha kolay.

Bununla birlikte, ServiceLocator'ı kullanan tüm sınıflar artık aynı zamanda bir bağlantı biçimi olan ServiceLocator'a bağlı olacaktır. Bu, ServiceLocator için bir arabirim kullanarak hafifletilebilir, böylece somut bir ServiceLocator uygulamasına bağlı kalmazsınız, ancak sınıflarınız bir tür Konum Belirleyicinin varlığına bağlı olurken, bir ServiceLocator kullanmamak, yeniden kullanımı önemli ölçüde artırır.

Gizli Bağımlılıklar

Bağımlılıkları gizleme sorunu çok fazla var. Yer belirleyiciyi tüketen sınıflarınıza enjekte ettiğinizde, herhangi bir bağımlılık bilemezsiniz. Ancak Singleton'ın aksine, SL genellikle perde arkasında ihtiyaç duyulan tüm bağımlılıkları somutlaştıracaktır. Dolayısıyla, bir Hizmeti aldığınızda , CreditCard örneğindeki Misko Hevery gibi olmazsınız , örneğin, bağımlılıkların tüm bağımlılıklarını elle başlatmanız gerekmez.

Bağımlılıkları örneğin içinden almak, ortak çalışanlara girmemeniz gerektiğini belirten Demeter Yasasını da ihlal ediyor. Bir örnek yalnızca en yakın işbirlikçileriyle konuşmalıdır. Bu, hem Singleton hem de ServiceLocator ile ilgili bir sorundur.

Küresel Durum

Küresel Durum problemi de bir şekilde hafifletilmiştir, çünkü testler arasında yeni bir Servis Bulucu başlattığınızda, daha önce yaratılan tüm örnekler de silinir (hatayı yapıp bunları SL'deki statik özniteliklere kaydetmediyseniz). Elbette bu, SL tarafından yönetilen sınıflarda herhangi bir küresel durum için geçerli değildir.

Ayrıca çok daha derinlemesine bir tartışma için Service Locator ve Dependency Injection'daki Fowler'a bakın .


Güncellemeniz ve Singletons kullanan kodun test edilmesiyle ilgili Sebastian Bergmann tarafından yazılan bağlantılı makale hakkında bir not : Sebastian, önerilen geçici çözümün Singleon'ları daha az sorun haline getirdiğini hiçbir şekilde önermiyor. Aksi takdirde daha fazla test edilebilirliği test etmek imkansız olacak olan kod yapmanın yalnızca bir yoludur. Ama yine de sorunlu bir kod. Aslında, açıkça şunu belirtiyor: "Sadece Yapabildiğin İçin, Yapman gerektiği anlamına gelmez".


1
Özellikle test edilebilirlik burada zorunlu kılınmalıdır. Statik yöntem çağrılarıyla alay edemezsiniz. Bununla birlikte, yapıcı veya ayarlayıcı aracılığıyla enjekte edilen hizmetlerle alay edebilirsiniz.
David

44

Servis bulucu modeli bir anti-modeldir. Bağımlılıkları açığa çıkarma problemini çözmez (bir sınıfın tanımına bakarak bağımlılıklarının ne olduğunu söyleyemezsiniz çünkü enjekte edilmiyorlar, bunun yerine hizmet bulucudan çekiliyorlar).

Öyleyse sorunuz şudur: servis bulucular neden iyidir? Cevabım: onlar değil.

Kaçının, kaçının, kaçının.


6
Arayüzler hakkında hiçbir şey bilmiyorsunuz gibi görünüyor. Sınıf sadece yapıcı imzasında gerekli arabirimi tanımlar - ve tüm bilmesi gereken budur. Geçti Servis Bulucu arayüz uygulamalıdır, hepsi bu. IDE arayüzün uygulanmasını kontrol edecekse, herhangi bir değişikliği kontrol etmek oldukça kolay olacaktır.
OZ_

4
@ yes123: Bunun yanlış olduğunu söyleyen insanlar ve yanılıyorlar çünkü SL bir anti-modeldir. Sorunuz, "SL neden iyi?" Cevabım: onlar değil.
jason

5
SL'nin bir anit-kalıp olup olmadığını tartışmayacağım, ancak söyleyeceğim şey, tekil ve küresellere kıyasla çok daha az kötülükler olduğu. Bir tektona bağlı bir sınıfı test edemezsiniz, ancak bir SL'ye bağlı bir sınıfı kesinlikle test edebilirsiniz (SL tasarımını çalışmadığı noktaya kadar bozabilirsiniz) ... Yani buna değer
noting

3
@Jason, Arabirimi uygulayan nesneyi iletmeniz gerekir - ve yalnızca bilmeniz gereken şey budur. Kendinizi yalnızca sınıf yapıcı tanımıyla sınırlandırıyorsunuz ve yapıcıda tüm sınıfları (arayüzleri değil) yazmak istiyorsunuz - bu aptalca bir fikir. Tek ihtiyacınız olan arayüz. Bu sınıfı taklitlerle başarılı bir şekilde test edebilirsiniz, kodu değiştirmeden kolayca davranışı değiştirebilirsiniz, fazladan bağımlılıklar ve eşleştirme yoktur - hepsi (genel olarak) Bağımlılık Enjeksiyonunda sahip olmak istediğimiz şeydir.
OZ_

2
Elbette, Veritabanını, Kaydediciyi, Disk'i, Şablonu, Önbelleği ve Kullanıcıyı tek bir "Girdi" nesnesinde bir araya getireceğim, nesnemin hangi bağımlılıklara dayandığını söylemek, bir kap kullanmama kıyasla kesinlikle daha kolay olacaktır.
Mahn

4

Servis kapsayıcısı, Singleton modelinin yaptığı gibi bağımlılıkları gizler. Servis konteynerinin tüm avantajlarına sahip olmasına rağmen (bildiğim kadarıyla) servis konteynerinin sahip olduğu dezavantajlara sahip olmadığı için, bunun yerine bağımlılık enjeksiyon konteynerlerini kullanmayı önerebilirsiniz.

Anladığım kadarıyla, ikisi arasındaki tek fark, servis konteynerinde, servis konteynerinin enjekte edilen nesne olmasıdır (dolayısıyla bağımlılıkları gizler), DIC kullandığınızda, DIC sizin için uygun bağımlılıkları enjekte eder. DIC tarafından yönetilen sınıf, bir DIC tarafından yönetildiği gerçeğinden tamamen habersizdir, bu nedenle daha az eşleştirme, açık bağımlılıklar ve mutlu birim testlerine sahip olursunuz.

Bu, SO'da her ikisinin de farkını açıklayan iyi bir sorudur: Bağımlılık Enjeksiyonu ve Hizmet Bulucu modelleri arasındaki fark nedir?


"DIC sizin için uygun bağımlılıkları enjekte eder" Bu Singleton'da da olmaz mı?
dinamik

5
@ yes123 - Bir Singleton kullanıyorsanız, onu enjekte etmezsiniz, çoğu zaman sadece küresel olarak erişirsiniz (Singleton'ın amacı budur). Sanırım eğer Singleton'u enjekte ederseniz , bağımlılıkları gizlemeyeceğini, ancak Singleton modelinin orijinal amacını bir nevi bozacağını söylerseniz, kendinize sorarsınız, bu sınıfa küresel olarak erişilmesine ihtiyacım yoksa, neden Singleton yapmam gerekiyor mu?
rickchristie

2

Çünkü, Hizmet Konteynerindeki nesneleri,
1) kalıtımla (Nesne Yöneticisi sınıfı miras alınabilir ve yöntemler geçersiz kılınabilir)
2) konfigürasyonu değiştirme (Symfony durumunda)

Tekil sesler yalnızca yüksek eşleşme nedeniyle değil, aynı zamanda Tekil sesler Tek ton kötüdür. Neredeyse her tür nesne için yanlış mimari.

'Pure' DI ile (kurucularda) çok büyük bir bedel ödersiniz - tüm nesneler yapıcıya aktarılmadan önce yaratılmalıdır. Daha fazla kullanılan bellek ve daha az performans anlamına gelecektir. Ayrıca, her zaman nesne oluşturucuda oluşturulup aktarılamaz - bağımlılıklar zinciri oluşturulabilir ... İngilizcem bunu tamamen tartışmak için yeterince iyi değil, Symfony belgelerinde okuyun.


0

Benim için basit bir nedenden ötürü global sabitlerden, tek tonlardan kaçınmaya çalışıyorum, API'lerin çalışmasına ihtiyaç duyabileceğim durumlar var.

Örneğin, kullanıcı arabirimim ve yöneticim var. Yönetici içinde, bir kullanıcı olarak giriş yapabilmelerini istiyorum. Yönetici içindeki kodu düşünün.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

Bu, ön uç başlatma için yeni veritabanı bağlantısı, yeni kaydedici vb. Kurabilir ve kullanıcının gerçekten var olup olmadığını, geçerli olup olmadığını vb. Kontrol edebilir. Ayrıca uygun ayrı tanımlama bilgisi ve konum hizmetlerini de kullanır.

Benim singleton ile ilgili fikrim - Aynı nesneyi ebeveyn içine iki kez ekleyemezsiniz. Örneğin

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

sizi tek bir örnek ve her iki değişken de ona işaret eden bir şekilde bırakır.

Son olarak, nesne yönelimli geliştirme kullanmak istiyorsanız, sınıflarla değil nesnelerle çalışın.


1
yani metodunuz $api var'ı çerçevenizin etrafından geçirmek mi? Ne demek istediğini tam olarak anlamadım. Ayrıca çağrı add('Logger')aynı örneği döndürürse temelde bir hizmet kodlayıcınız var
dinamik

Evet bu doğru. Onlardan "Sistem Denetleyicisi" olarak bahsediyorum ve API'nin işlevselliğini geliştirmeleri amaçlanıyor. Benzer şekilde, bir modele iki kez "Denetlenebilir" denetleyici eklemek tamamen aynı şekilde çalışır - yalnızca bir örnek ve yalnızca bir denetim alanı kümesi oluşturun.
romaninsh
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.