Bağımlılık Enjeksiyonu ve Tekli Tasarım modeli


92

Bağımlılık enjeksiyonunu veya tekil kalıbı ne zaman kullanacağımızı nasıl belirleyebiliriz? "Tekil kalıp yerine Bağımlılık enjeksiyonu kullanın" dedikleri birçok web sitesinde okudum. Ama onlara tamamen katılıp katılmadığımdan emin değilim. Küçük veya orta ölçekli projelerim için kesinlikle tekli desen kullanımını basit görüyorum.

Örneğin Logger. Kullanabilirim Logger.GetInstance().Log(...) Ama bunun yerine neden oluşturduğum her sınıfa logger örneğiyle enjekte etmem gerekiyor?

Yanıtlar:


67

Bir testte nelerin kaydedildiğini doğrulamak istiyorsanız, bağımlılık enjeksiyonuna ihtiyacınız var. Ayrıca, bir kaydedici nadiren tekildir - genellikle her sınıfınız için bir kaydediciniz vardır.

Test edilebilirlik için Nesne odaklı tasarım hakkındaki bu sunumu izleyin ve tekillerin neden kötü olduğunu göreceksiniz.

Tekillerle ilgili sorun, özellikle testlerde tahmin edilmesi zor olan küresel bir durumu temsil etmeleridir.

Bir nesnenin fiilen tek başına olabileceğini, ancak yine de yoluyla değil, bağımlılık enjeksiyonu yoluyla elde edilebileceğini unutmayın Singleton.getInstance().

Misko Hevery'nin sunumunda değindiği bazı önemli noktaları listeliyorum. Bunu izledikten sonra bunu bir nesne tanımlamak için neden daha iyi olduğunu tam perspektif kazanacak neyi bağımlılıkları vardır, ama bir yolunu tanımlamak değil nasıl oluşturulacağını onları.


89

Singleton'lar komünizm gibidir: her ikisi de kağıt üzerinde harika ses çıkarır, ancak pratikte problemlerle patlar.

Tekil desen, nesnelere erişim kolaylığına orantısız bir vurgu yapar. Her tüketicinin AppDomain kapsamlı bir nesne kullanmasını gerektirerek bağlamdan tamamen kaçınır ve değişen uygulamalar için hiçbir seçenek bırakmaz. Tamamen sıfır ifade gücü GetInstance()eklerken , altyapı bilgisini sınıflarınıza yerleştirir (çağrı ) . Ne için değiştirmeden bir sınıfı tarafından kullanılan uygulama değiştiremezsiniz çünkü Aslında, sizin ifade gücünü azaltır hepsi bunlardan. Tek seferlik işlevsellik ekleyemezsiniz.

Sınıf Ayrıca, ne zaman Foobağlıdır Logger.GetInstance(), Fooetkin bir tüketicilerden bağımlılıklarından saklıyor. Bu Foo, kaynağını okumadıkça ve bağlı olduğu gerçeğini ortaya çıkarmadıkça onu tam olarak anlayamayacağınız veya güvenle kullanamayacağınız anlamına gelir Logger. Kaynağa sahip değilseniz, bu, bağlı olduğunuz kodu ne kadar iyi anlayabileceğinizi ve etkili bir şekilde kullanabileceğinizi sınırlar.

Statik özelliklerle / yöntemlerle uygulandığı şekliyle tekli desen, bir altyapının uygulanmasına yönelik bir saldırıdan biraz daha fazlasıdır. Alternatifler üzerinde fark edilebilir bir fayda sunmazken sizi sayısız şekilde sınırlar. İstediğiniz gibi kullanabilirsiniz, ancak daha iyi tasarımı destekleyen uygulanabilir alternatifler olduğu için, bu asla önerilen bir uygulama olmamalıdır.


9
@BryanWatts dedi ki, Singleton'lar normal bir orta ölçekli uygulamada hala çok daha hızlı ve daha az hataya meyillidir. Genelde bir (özel) Logger için birden fazla olası uygulamam yok, bu yüzden neden ** yapmam gerekir: 1. Bunun için bir arayüz oluşturun ve genel üyeler eklemem / değiştirmem gerektiği her seferinde değiştirin 2. Bakım a DI yapılandırması 3. Tüm sistemde bu türden tek bir nesne olduğu gerçeğini gizleyin 4. Kendimi zamanından önce Ayrılma Kaygılarının neden olduğu katı bir işlevselliğe bağlıyorum.
Uri Abramson

@UriAbramson: Görünüşe göre tercih ettiğiniz değiş tokuşlar hakkında karar vermişsiniz, bu yüzden sizi ikna etmeye çalışmayacağım.
Bryan Watts

@BryanWatts Durum böyle değil, belki de yorumum biraz fazla sertti ama aslında bahsettiğim konu hakkında söyleyeceklerinizi duymak beni çok ilgilendiriyor ...
Uri Abramson

2
@UriAbramson: Yeterince adil. En azından test sırasında uygulamaların izolasyon için değiştirilmesinin önemli olduğu konusunda hemfikir misiniz?
Bryan Watts

6
Tekil kullanımı ve bağımlılık enjeksiyonu birbirini dışlamaz. Bir singleton bir arabirim uygulayabilir, bu nedenle başka bir sınıfa bağımlılığı sağlamak için kullanılabilir. Tekil olması, her tüketiciyi "GetInstance" yöntemi / özelliği aracılığıyla bir referans almaya zorlamaz.
Oliver

19

Diğerleri sorunu genel olarak tekli tonlarla çok iyi açıkladı. Logger'ın özel durumu hakkında bir not eklemek istiyorum. Statik getInstance()veya getRootLogger()yöntem aracılığıyla bir Logger'a (veya kesin olarak kök kaydediciye) tekil olarak erişmenin genellikle sorun olmadığı konusunda sizinle aynı fikirdeyim . (eğer test ettiğiniz sınıf tarafından nelerin kaydedildiğini görmek istemiyorsanız - ancak deneyimlerime göre bunun gerekli olduğu bu tür durumları pek hatırlayamıyorum. Sonra yine, diğerleri için bu daha acil bir sorun olabilir).

IMO, test ettiğiniz sınıfla ilgili herhangi bir durum içermediğinden, genellikle tek bir kaydedici endişe değildir. Yani, kaydedicinin durumunun (ve olası değişikliklerinin) test edilen sınıfın durumu üzerinde hiçbir etkisi yoktur. Dolayısıyla birim testlerinizi daha da zorlaştırmaz.

Alternatif olarak, günlükçüyü oluşturucu aracılığıyla uygulamanızdaki (neredeyse) her sınıfa enjekte etmek olabilir. Alternatif o noktada keşfettiklerinde olurdu - arayüzleri tutarlılık için, söz konusu sınıf şu anda herhangi bir şey log olmasa bile enjekte edilmelidir şimdi böylece, bu sınıftan bir şey giriş yapmanız, bir logger gerek DI için tüm istemci kodunu bozarak bir yapıcı parametresi eklemeniz gerekir. Bu iki seçenekten de hoşlanmıyorum ve günlük kaydı için DI kullanmanın, herhangi bir somut fayda olmaksızın teorik bir kurala uymak için hayatımı karmaşıklaştıracağını düşünüyorum.

Sonuç olarak benim alt satırım: (neredeyse) evrensel olarak kullanılan, ancak uygulamanızla ilgili durumu içermeyen bir sınıf, Singleton olarak güvenle uygulanabilir .


1
O zaman bile, bir tekil bir acı olabilir. Kaydedicinizin bazı durumlarda ek parametreye ihtiyaç duyduğunu fark ederseniz, ya yeni bir yöntem yaparsınız (ve kaydedicinin çirkinleşmesine izin verirsiniz) ya da tüm tüketicileri umursamasalar bile bozarsınız.
kyoryu

4
@kyoryu, IMHO'nun (fiili) standart bir günlükleme çerçevesi kullanmayı ima ettiği "olağan" durumdan bahsediyorum. (Bunlar tipik olarak özellik / XML dosyaları btw ile yapılandırılabilir.) Elbette istisnalar da var - her zamanki gibi. Uygulamamın bu açıdan olağanüstü olduğunu biliyorsam, gerçekten bir Singleton kullanmayacağım. Ancak aşırı mühendislik, "bu bazen yararlı olabilir" demek neredeyse her zaman boşa harcanan bir çabadır.
Péter Török

Zaten DI kullanıyorsanız, bu fazladan bir mühendislik değildir. (BTW, sana katılmıyorum, ben olumlu oyum). Birçok kaydedici, bazı "kategori" bilgileri veya buna benzer bir şey gerektirir ve ek parametreler eklemek acıya neden olabilir. Bir arayüzün arkasına gizlemek, tüketen kodu temiz tutmaya yardımcı olabilir ve farklı bir günlük çerçevesine geçişi kolaylaştırabilir.
kyoryu

1
@kyoryu, yanlış varsayım için özür dilerim. Bir olumsuz oy ve bir yorum gördüm, bu yüzden noktaları birleştirdim - yanlış yoldan, ortaya çıktı :-( Farklı bir kayıt çerçevesine geçme ihtiyacını hiç yaşamadım, ama yine de bunun yasal olabileceğini anlıyorum bazı projelerde endişe.
Péter Török

5
Yanılıyorsam düzeltin, ancak tekil LogFactory için değil, logger için olacaktır. Ayrıca LogFactory muhtemelen apache commons veya Slf4j loglama cephesi olacaktır. Bu nedenle, günlük kaydı uygulamalarını değiştirmek zaten zahmetsizdir. Bir LogFactory'yi enjekte etmek için DI kullanmanın asıl acısı, uygulamanızdaki her örneği oluşturmak için şimdi applicationContext'e gitmeniz gerektiği gerçeği değil mi?
HDave

9

Çoğunlukla, ancak tamamen testlerle ilgili değil. Singltonlar popülerdi çünkü onları tüketmek kolaydı, ancak tekillerin bazı dezavantajları var.

  • Test etmesi zor. Kaydedicinin doğru şeyi yaptığından nasıl emin olacağım anlamına geliyor.
  • Test etmesi zor. Yani, kaydediciyi kullanan kodu test ediyorsam, ancak bu benim testimin odak noktası değilse, yine de test ortamımın kaydediciyi desteklediğinden emin olmam gerekir.
  • Bazen bir bekar istemezsin, ama daha esnek

DI size bağımlı sınıflarınızın kolay kullanımını sağlar - sadece yapıcı parametrelerine koyun ve sistem bunu size sağlar - aynı zamanda size test ve yapım esnekliği verir.


Pek sayılmaz. Paylaşılan değişken durum ve statik bağımlılıklar uzun vadede işleri acı verici hale getiriyor. Test, sadece açık ve çoğu zaman en acı veren örnektir.
kyoryu

2

Bağımlılık Enjeksiyonu yerine bir Singleton kullanmanız gereken tek zaman, Singleton'un List.Empty veya benzeri gibi (değişmez listeler varsayılarak) değişmez bir değeri temsil etmesidir.

Bir Singleton için bağırsak kontrolü "Bu bir Singleton yerine global bir değişken olsaydı uygun olur muydum?" Değilse, global bir değişkeni yazmak için Singleton modelini kullanıyorsunuz ve farklı bir yaklaşım düşünmelisiniz.


1

Monostate makalesine baktım - bu Singleton'a güzel bir alternatif, ancak bazı garip özelliklere sahip:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

Bu tür korkutucu değil mi - çünkü Eşleştirici, save () işlemini gerçekleştirmek için veritabanı bağlantısına gerçekten bağımlıdır - ancak daha önce başka bir eşleyici oluşturulmuşsa, bağımlılıklarını edinirken bu adımı atlayabilir. Derli toplu olsa da, biraz dağınık değil mi?


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.