Bağımlılık enjeksiyonunu bir kapla kullanmak ile bir servis bulucu kullanmak arasındaki fark nedir?


107

Bir sınıf içinde doğrudan somutlaştırıcı bağımlılıkların kötü bir uygulama olarak kabul edildiğini anlıyorum. Bu, testlerin çok zor olduğu her şeyi çok sıkı bir şekilde yapmak gibi bir anlam ifade ediyor.

Karşılaştığım çerçevelerin hemen hemen tümü, hizmet konumlayıcıları kullanmaya bağlı bir kapla bağımlılık enjeksiyonunu tercih ediyor gibi görünüyor. Her ikisi de, bir sınıf bağımlılık gerektirdiğinde programcının hangi nesnenin döndürülmesi gerektiğini belirtmesine izin vererek aynı şeyi başarmış gibi görünmektedir.

İkisi arasındaki fark nedir? Neden birini diğerinden seçeyim?


3
Bu StackOverflow'ta sorulmuş (ve cevaplandırılmış): stackoverflow.com/questions/8900710/…
Kyralessa

2
Kanımca, geliştiricilerin, hizmet konumlandırıcılarının bağımlılık enjeksiyonu olduğunu düşünerek başkalarını aldatması hiç de nadir değildir. Bunu yaparlar çünkü bağımlılık enjeksiyonunun çoğu zaman daha ileri olduğu düşünülmektedir.
Gherman


1
Hiç kimsenin Hizmet Belirleyicileri ile geçici bir kaynağın ne zaman tahrip edilebileceğini bilmek daha zor olduğunu söylemediğine şaşırdım. Her zaman birincil nedeni olduğunu düşündüm.
Buh Buh

Yanıtlar:


126

Nesnenin kendisi, bir kurucu aracılığıyla kabul etmenin aksine, bağımlılıklarını istemekten sorumlu olduğunda, bazı temel bilgileri gizlemektedir. newBağımlılıklarını somutlaştırmak için kullanılan çok sıkı bağlı durumdan sadece biraz daha iyidir . Bağlanmayı azaltır, çünkü gerçekte aldığı bağımlılıkları değiştirebilirsiniz, ancak hala sallayamayacağı bir bağımlılığı vardır: servis bulucu. Her şeyin bağımlı olduğu şey bu olur.

Yapıcı argümanlarla bağımlılıkları sağlayan bir konteyner en fazla açıklığı verir. Biz bir nesne hem bir ihtiyacı olduğunu ön sağ bakın AccountRepositoryve PasswordStrengthEvaluator. Bir servis bulucu kullanılırken, bu bilgi hemen daha az belirgindir. Hemen bir nesnenin, 17 bağımlılığın olduğu bir durumu görür ve kendinize “Hmm, bu çok fazla görünüyor. Orada neler oluyor?” Diyorsunuz. Bir servis bulucuya yapılan çağrılar çeşitli yöntemlerin etrafına yayılabilir ve koşullu mantığın arkasına gizlenebilir ve her şeyi yapan bir “Tanrı sınıfı” yarattığınızı fark etmeyebilirsiniz. Belki bu sınıf daha odaklı ve dolayısıyla daha test edilebilir 3 daha küçük sınıfa dönüştürülebilir.

Şimdi test etmeyi düşünün. Bir nesne bağımlılıklarını almak için bir servis bulucu kullanıyorsa, test çerçevenizin bir servis bulucuya da ihtiyacı olacaktır. Bir testte, servis bulucuyu test edilen nesneye bağımlılıkları sağlayacak şekilde konfigüre edersiniz - belki a FakeAccountRepositoryve a VeryForgivingPasswordStrengthEvaluatorve sonra testi çalıştırın. Ancak bu, nesnenin yapıcısındaki bağımlılıkları belirtmekten daha fazla iş. Test çerçeveniz de servis bulucuya bağlı hale gelir. Yazma testlerini daha az çekici hale getiren her testte yapılandırmanız gereken başka bir şey bu.

Mark Seeman'ın bu konudaki makalesinde "Serivce Konum Belirleyici bir Anti-Paterndir" konusuna bakın. .Net dünyasındaysanız, kitabını alın. Çok iyi.


1
Bu soruyu okumak, C # üzerinden Adaptive Code hakkında , bu cevapla oldukça uyumlu olan Gary McLean Hall tarafından düşündüm . Hizmet bulucunun güvenliğin anahtarı olması gibi bazı iyi benzerlikleri vardır; Bir sınıfa verildiğinde, tespit edilmesi kolay olmayabilecek irade bağımlılıkları yaratabilir.
Dennis,

10
Bir şey IMO ilave edilmesi gerekmektedir constructor supplied dependenciesVS service locatoreski bir olmasıdır edilebilir ikincisi bir süre, derleme zamanı verfied sadece çalışma zamanını doğrulanacak.
andras

2
Ek olarak, bir servis bulucu, bir bileşenin birçok bağımlılığa sahip olmasını engellememektedir. Yapıcınıza 10 parametre eklemek zorundaysanız, kodunuzda bir sorun olduğunu oldukça iyi hissedersiniz. Bununla birlikte, bir servis bulucuya sadece 10 statik çağrı yaparsanız, bir tür sorun yaşayacağınız açıkça belli olmayabilir. Servis bulucu ile büyük bir proje üzerinde çalıştım ve bu en büyük sorun oldu. Oturmak, yansıtmak, yeniden tasarlamak ve yeniden yansıtmak yerine yeni bir yola kısa devre yapmak çok kolaydı.
Pace

1
Test hakkında - İtiraz etmek But that's more work than specifying dependencies in the object's constructor.istiyorum. Bir servis bulucu ile yalnızca testiniz için ihtiyacınız olan 3 bağımlılığı belirtmeniz yeterlidir. Yapıcı tabanlı bir DI ile, 7 kullanılmamış olsa bile, bunların TÜM 10'unu belirtmeniz gerekir.
Vilx

4
@ Vilx- Birden fazla kullanılmamış yapıcı parametreniz varsa, o zaman sınıfınız Tek Sorumluluk ilkesini ihlal ediyor olabilir.
RB.

79

Bir fabrikada ayakkabı üreten bir işçi olduğunuzu hayal edin .

Ayakkabıların montajından siz sorumlusunuz ve bu yüzden bunu yapmak için birçok şeye ihtiyacınız olacak.

  • Deri
  • Ölçüm bandı
  • Tutkal
  • Çiviler
  • Çekiç
  • Makas
  • Ayakkabı bağcıkları

Ve bunun gibi.

Fabrikada iştesiniz ve çalışmaya hazırsınız. Nasıl devam edeceğinize ilişkin talimatların bir listesi var, ancak henüz malzeme veya araçlarınız yok.

Bir Servis Belirleyici , ihtiyacınız olanı almanıza yardımcı olabilecek bir Foreman gibidir.

Servis Belirleyiciye birşeye her ihtiyacınız olduğunda sorarsınız ve sizin için bulmaya giderler. Servis Bulucuya ne isteyeceğiniz ve onu nasıl bulacağınız konusunda önceden bilgi verildi.

Umarım beklenmedik bir şey istemeyeceğinizi umarsınız. Yer Belirleyici, belirli bir araç veya malzeme hakkında önceden bilgi sahibi olmadıysa, onu sizin için alamayacaklar ve sadece size silecekler.

servis bulucu

Bir Bağımlılık Enjeksiyonu (DI) Konteyner , gün başında herkesin ihtiyaç duyduğu her şeyle dolu olan büyük bir kutu gibidir.

Fabrika çalışmaya başladığında, Kompozisyon Kökü olarak bilinen Büyük Patron kabı kapar ve her şeyi Hat Yöneticisine verir .

Bölüm Müdürleri şimdi görevlerini o gün için yerine getirmek için ihtiyaç duydukları şeye sahipler. Sahip olduklarını alırlar ve astlarına gerekenleri geçer.

Bu süreç, üretim hattını dağıtan bağımlılıklar ile devam ediyor. Sonunda Foreman'ınız için bir malzeme ve araç kabı ortaya çıkıyor.

Foreman'ınız şimdi size ve diğer çalışanlara tam olarak ihtiyaç duyduğunuz şeyi, hatta sizden sormadan dağıtıyor.

Temel olarak, işe gelir gelmez, ihtiyacınız olan her şey zaten sizi bekleyen bir kutuda. Onları nasıl alacağınız hakkında hiçbir şey bilmenize gerek yoktu.

bağımlılık enjeksiyon kabı


45
Bu, bu 2 şeyin her birinin ne olduğuna dair mükemmel bir açıklama, süper iyi görünümlü diyagramlar! Ancak bu, "Neden birini diğerinden seçmelisiniz" sorusuna cevap vermiyor?
Matthieu M.

21
“Beklenmedik bir şey istemeyeceğinizi ümit edersiniz. Belirleyici belirli bir araç veya malzeme hakkında önceden bilgi sahibi olmasaydı” Ama bu aynı zamanda bir DI kabı için de geçerli olabilir.
Kenneth K.

5
@FrankHopkins DI kapsayıcınıza bir arabirim / sınıf kaydetmeyi ihmal ederseniz, kodunuz hala derlenir ve çalışma zamanında derhal başarısız olur.
Kenneth K.

3
Her ikisinin de nasıl kurulduğuna bağlı olarak başarısız olma ihtimali de yüksektir. Ancak bir DI kapsayıcısıyla, X sınıfının gerçekten bir bağımlılığa ihtiyaç duyduğu durumlarda, X sınıfından sonra yapılmak yerine, bir sorunla daha erken karşılaşmanız daha olasıdır. Tartışmalı olarak daha iyi, çünkü sorunla karşılaşmak, onu bulmak ve çözmek daha kolay. Ancak Kenneth ile aynı fikirdeyim, cevabın bu sorunun sadece servis yer belirleyicileri için olduğunu öne sürüyor, ki bu kesinlikle böyle değil.
GolezTrol

3
Harika cevap, ama bence başka bir şeyi özlüyor; Eğer bir fil için herhangi bir aşamada ustabaşı sormak ve o ise bu durum geçerlidir gelmez almak için biliyorum o bunu gerekmez rağmen hiçbir soru sordu-sizin için bir tane alacak. Ve yerde bir problemi hata ayıklamaya çalışan biri (eğer bir geliştirici varsa), wtf'nin bu masanın üzerinde yaptığı bir fil olduğunu merak eder, bu yüzden onların neyin yanlış gittiğini düşünmelerini zorlaştırır. Oysa DI ile birlikte, işçi sınıfı, işe başlamadan önce ihtiyacınız olan her bağımlılığı önceden ilan eder , böylece gerekçelendirmeyi kolaylaştırır.
Stephen Byrne

10

Web'i araştırırken bulduğum birkaç ekstra nokta:

  • Yapıcıya bağımlılıkları enjekte etmek, bir sınıfın neye ihtiyacı olduğunu anlamayı kolaylaştırır. Modern IDE'ler, yapıcının kabul ettiği argümanları ve türlerini ima edecektir. Bir servis bulucu kullanıyorsanız, hangi bağımlılıkların gerekli olduğunu bilmeden önce sınıfı okumalısınız.
  • Bağımlılık enjeksiyonu, servis tespit etmekten çok "söyleme" ilkesine bağlı görünüyor. Bir bağımlılığın belirli bir tipte olmasını zorunlu kılarak, hangi bağımlılıkların gerekli olduğunu “söylersiniz”. Gerekli bağımlılıkları geçmeden sınıfı harekete geçirmek imkansızdır. Bir servis bulucu ile bir servis isteyin "ve servis bulucu doğru yapılandırılmamışsa, gerekli olanı alamayabilirsiniz.

4

Bu partiye geç geleceğim ama direnemiyorum.

Bağımlılık enjeksiyonunu bir kapla kullanmak ile bir servis bulucu kullanmak arasındaki fark nedir?

Bazen hiç yok. Farkı yaratan şey, ne hakkında ne bildiğidir.

Bağımlılığı arayan müşteri konteyneri bildiğinde bir servis bulucu kullandığınızı biliyorsunuz. Bağımlılıklarını nasıl bulacağını bilen bir müşteri, onları almak için bir konteynırdan geçerken bile, servis bulma modelidir.

Bu, servis bulucudan kaçınmak istediğinizde konteyner kullanamayacağınız anlamına mı geliyor? Hayır. Sadece müşterileri konteynır hakkında bilmek istemesin. En önemli fark kabı kullandığınız yer.

Clientİhtiyaçları söyleyelim Dependency. Kap bir a Dependency.

class Client { 
    Client() { 
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        this.dependency = (Dependency) beanfactory.getBean("dependency");        
    }
    Dependency dependency;
}

Servis bulma modelini yeni takip ettik çünkü Clientnasıl bulacağımızı biliyor Dependency. Elbette kodlanmış bir kod kullanıyor ClassPathXmlApplicationContextancak Clientaramalar nedeniyle hala bir servis bulucunuz olduğunu enjekte etseniz bile beanfactory.getBean().

Servis bulucuyu önlemek için bu kabı terketmek zorunda değilsiniz. Sadece onu dışarıya taşımalısın, Cliento yüzden Clientbilmiyor.

class EntryPoint { 
    public static void main(String[] args) {
        BeanFactory beanfactory = new ClassPathXmlApplicationContext("Beans.xml");
        Client client = (Client) beanfactory.getBean("client");

        client.start();
    }
}

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dependency" class="Dependency">
    </bean>

    <bean id="client" class="Client">
        <constructor-arg value="dependency" />        
    </bean>
</beans>

ClientŞimdi konteynerin ne olduğu hakkında hiçbir fikrinin olmadığını görün.

class Client { 
    Client(Dependency dependency) { 

        this.dependency = dependency;        
    }
    Dependency dependency;
}

Kabı tüm istemcilerden çıkarın ve tüm uzun ömürlü nesnelerinize ait bir nesne grafiği oluşturabileceği ana yere yapıştırın. Üzerine bir yöntem çıkarmak ve çağırmak için bu nesnelerden birini seçin ve tüm grafiği tıklamaya başlayın.

Bu, tüm statik konstrüksiyonu konteynır XML'e taşır, ancak tüm müşterilerinizi bağımlılıklarını nasıl bulabileceklerini hevesle cahil tutarlar.

Fakat ana hala bağımlılıkları nasıl bulacağını biliyor! Evet öyle. Ancak bu bilgiyi etrafa yayarak değil, servis bulucunun temel probleminden kaçınırsınız. Bir konteyner kullanma kararı artık tek bir yerde yapılıyor ve yüzlerce müşteriyi yeniden yazmadan değiştirilebilir.


1

İkisi ile neden DI konteynerinin neden bir servis belirleyiciden daha iyi olduğunu anlamanın en kolay yolunun neden bağımlılık inversiyonu yaptığımızı düşünmek olduğunu düşünüyorum.

Bağımlılık inversiyonu yapıyoruz, böylece her sınıf açıkça operasyon için neye bağlı olduğunu belirtir . Bunu yapıyoruz çünkü bu, elde edebileceğimiz en gevşek eşleşmeyi yaratıyor. Kaplin ne kadar gevşerse, test etmek ve yeniden yönlendirmek daha kolay bir şeydir (ve kodun daha temiz olması nedeniyle genellikle gelecekte en az yeniden düzenlemeyi gerektirir).

Aşağıdaki sınıfa bakalım:

public class MySpecialStringWriter
{
  private readonly IOutputProvider outputProvider;
  public MySpecialFormatter(IOutputProvider outputProvider)
  {
    this.outputProvider = outputProvider;
  }

  public void OutputString(string source)
  {
    this.outputProvider.Output("This is the string that was passed: " + source);
  }
}

Bu sınıfta, açıkça bir IOutputProvider'a ve bu dersin çalışmasını sağlamak için başka hiçbir şeye ihtiyacımız olmadığını belirtiyoruz. Bu tamamen test edilebilir ve tek bir arayüze bağlı. Bu sınıfı, uygulamamın herhangi bir yerine taşıyabilirim, farklı bir proje de dahil ve tek ihtiyacı IOutputProvider arayüzüne erişim. Diğer geliştiriciler bu sınıfa ikinci bir bağımlılık gerektiren yeni bir şey eklemek isterlerse , kurucuda ihtiyaç duydukları şey hakkında net olmaları gerekir.

Servis bulucu ile aynı sınıfa bir göz atın:

public class MySpecialStringWriter
{
  private readonly ServiceLocator serviceLocator;
  public MySpecialFormatter(ServiceLocator serviceLocator)
  {
    this.serviceLocator = serviceLocator;
  }

  public void OutputString(string source)
  {
    this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

Şimdi hizmet bulucuyu bağımlılık olarak ekledim. İşte hemen aşikar olan problemler:

  • Bununla ilgili ilk sorun , aynı sonucu elde etmek için daha fazla kod gerektirmesidir. Daha fazla kod bozuk. Daha fazla kod değil ama yine de daha fazla.
  • İkinci sorun, bağımlılığımın artık açık olmamasıdır . Hala sınıfa bir şey enjekte etmem gerekiyor. Şimdi hariç, istediğim şey açık değil. İstediğim şeyin özelliğine gizlenmiş. Şimdi sınıfı farklı bir düzeneğe taşımak istersem, hem ServiceLocator hem de IOutputProvider'a erişmem gerekiyor.
  • Üçüncü sorun, sınıfa kod eklerken aldıklarını bile anlamayan başka bir geliştirici tarafından ek bir bağımlılığın alınabilmesidir .
  • Son olarak, bu kodu test etmek daha zordur (ServiceLocator bir arayüz olsa bile) çünkü sadece IOutputProvider yerine ServiceLocator ve IOutputProvider ile alay etmek zorundayız

Öyleyse neden servis bulucuyu statik bir sınıf yapmıyoruz? Hadi bir bakalım:

public class MySpecialStringWriter
{
  public void OutputString(string source)
  {
    ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
  }
}

Bu çok daha basit, değil mi?

Yanlış.

Diyelim ki, IOutputProvider, diziyi dünyanın dört bir yanındaki on beş farklı veritabanında yazan ve tamamlanması çok uzun süren çok uzun süredir çalışan bir web servisi tarafından uygulanıyor.

Bu sınıfı test etmeye çalışalım. Test için farklı bir IOutputProvider uygulamasına ihtiyacımız var. Testi nasıl yazıyoruz?

Bunu yapmak için, test tarafından çağrıldığında farklı bir IOutputProvider uygulamasını kullanmak için statik ServiceLocator sınıfında bazı fantezi yapılandırmalar yapmamız gerekir. Bu cümleyi yazmak bile acı vericiydi. Uygulanması zorlaşacak ve bakım kabusu olacaktı . Özellikle sınamak için denediğimiz sınıf değilse, sınava yönelik bir sınıfı hiçbir zaman özel olarak değiştirmememiz gerekmez.

Öyleyse şimdi ikisinden birine bıraktınız: a) ilgisiz ServiceLocator sınıfında rahatsız edici kod değişikliklerine neden olan bir test; veya b) hiç test yok. Ve daha az esnek bir çözümle baş başa kaldın.

Yani hizmet bulucu sınıfı vardır yapıcı içine enjekte edilecek. Bu, daha önce bahsettiğimiz spesifik problemlerden ayrıldığımız anlamına gelir. Hizmet bulucu daha fazla kod gerektirir, diğer geliştiricilere ihtiyaç duymadığı şeylere ihtiyacı olduğunu söyler, diğer geliştiricilere daha kötü kod yazmasını teşvik eder ve bize ileriye doğru daha az esneklik sağlar.

Basitçe söylemek gerekirse servis bulucuları bir uygulamada eşleşmeyi arttırır ve diğer geliştiricilerin yüksek eşleşmiş kod yazmasını teşvik eder .


Servis Belirleyiciler (SL) ve Bağımlılık Enjeksiyonu (DI), benzer şekilde aynı sorunu çözer. Tek fark, bağımlılığınız için "söylüyorsanız" DI veya "soruyorsanız".
Matthew Whited

@MatthewWhited Ana fark, bir servis bulucu ile aldığınız dolaylı bağımlılıkların sayısıdır. Ve bu, kodun uzun vadede sürdürülebilirliği ve istikrarı için büyük bir fark yaratıyor.
Stephen

Bu gerçekten değil. Her iki şekilde de aynı sayıda bağımlılığa sahipsiniz.
Matthew Whited

Başlangıçta evet, ancak bağımlılıklar alıp almadığınızı fark etmeden bir servis bulucu ile bağımlılıklar alabilirsiniz. İlk başta bağımlılık inversiyonu yaptığımızın sebebinin büyük bir kısmını yendi. Bir sınıf A ve B özelliklerine, bir diğeri B ve C'ye bağlıdır. Birden Servis Yeri Belirleyici bir tanrı sınıfı haline gelir. DI konteyner yok ve iki sınıf uygun şekilde yalnızca sırasıyla A ve B ve B ve C'ye bağlıdır. Bu, onları yeniden yüzlerce kez daha kolay hale getirir. Hizmet Konumlandırıcılar, DI ile aynı görünmeleri konusunda sinsidir ancak değildir.
Stephen,
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.