Neden bağımlılık enjeksiyonunu kullanmalıyım?


95

Bağımlılık enjeksiyonunu neden kullanmam gerektiğine dair kaynakları aramakta zorlanıyorum . Gördüğüm kaynakların çoğu bir nesnenin bir örneğini bir nesnenin başka bir örneğine geçtiğini açıklıyor, ama neden? Bu sadece daha temiz mimari için mi / kod mu yoksa performansı bir bütün olarak etkiler mi?

Neden aşağıdakileri yapmalıyım?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Aşağıdaki yerine?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}

8
DeactivateProfile () (kötü olan) için kodlanmış bir bağımlılık tanıtıyorsunuz. İlkinde daha fazla ayrıştırılmış kod var, bu da değiştirmeyi ve test etmeyi kolaylaştırıyor.
Aulis Ronkainen,

3
İlkini neden yaptın? Bir Ayardan geçiyorsun ve sonra değerini görmezden geliyorsun.
Phil N DeBlanc

57
Olumsuz oylara katılmıyorum. Konu, uzmanlar için önemsiz olarak değerlendirilebilir olsa da, sorunun önemi vardır: Eğer bağımlılık inversiyonu kullanılacaksa, onu kullanmanın bir gerekçesi olmalı.
Flater

13
@PhilNDeBlanc: Bu kod açıkça basitleştirilmiştir ve gerçek dünya mantığının göstergesi değildir. Ancak, deactivateProfilebana isActiveönceki durumunu önemsemeden yanlış ayarına getirmenin burada doğru yaklaşım olduğunu öne sürüyor . Yöntemi doğal olarak çağırmak, onu geçerli (in) etkin durumunu elde etmemek için etkin değil olarak ayarlamak anlamına gelir .
Flater

2
Kodunuz bir bağımlılık enjeksiyonu veya inversiyon örneği değildir. Parametrelendirme örneği (genellikle DI'den çok daha iyidir).
jpmc26

Yanıtlar:


104

Avantaj, bağımlılık enjeksiyonu olmadan, Profil sınıfınızdır.

  • Ayarlar nesnesinin nasıl oluşturulacağını bilmesi gerekir (Tek Sorumluluk İlkesini ihlal eder)
  • Her zaman Ayarlar nesnesini aynı şekilde oluşturur (ikisi arasında sıkı bir bağlantı oluşturur)

Ancak bağımlılık enjeksiyonu ile

  • Ayarlar nesneleri oluşturma mantığı başka bir yerde
  • Farklı ayar nesnelerini kullanmak kolaydır

Bu, bu özel durumla ilgisiz görünebilir (veya hatta) önemsiz görünebilir, ancak bir Ayarlar nesnesi hakkında değil, farklı uygulamalara sahip bir DataStore nesnesi değil, bir dosyayı dosyalarda saklayan ve bir başkasını da saklayan bir başka nesneden bahsettiğimizi hayal edin. bir veritabanı Ve otomatikleştirilmiş testler için sahte bir uygulama da istiyorsunuz. Şimdi, Profile sınıfının hangisini kullandığını kodlamasını istemiyorsunuz - ve daha da önemlisi, gerçekten, Profile sınıfının dosya sistemi yolları, DB bağlantıları ve şifreleri hakkında bilgi sahibi olmasını gerçekten istemiyorsunuz, yani DataStore nesnelerinin oluşturulması başka bir yerde olmak zorunda.


23
This may seem (or even be) irrelevant in this particular caseAslında bunun çok alakalı olduğunu düşünüyorum. Ayarları nasıl aldın? Gördüğüm birçok sistemde kodlanmış bir varsayılan ayar kümesi ve genel kullanıma açık bir yapılandırma olacak, bu yüzden her ikisini de yüklemeniz ve bazı ayarların ortak ayarların üzerine yazmanız gerekir. Birden fazla varsayılan kaynağa bile ihtiyacınız olabilir. Belki biraz diskten, hatta DB'den bile alıyor olabilirsiniz. Bu nedenle, ayarları almak için bile tüm mantık önemsiz olabilir ve çoğu zaman önemsizdir - kesinlikle kod tüketen bir şey olmaz veya umursamaz.
VLAZ

Ayrıca, bir web servisi gibi önemsiz bir bileşen için nesne başlatmanın $setting = new Setting();korkunç derecede verimsiz olacağını da söyleyebiliriz . Enjeksiyon ve nesne başlatması bir kez olur.
vikingsteve

9
Test için alay kullanmanın daha fazla vurgulanması gerektiğini düşünüyorum. Muhtemelen sadece koda bakarsanız, her zaman bir Ayarlar nesnesi olacak ve asla değişmeyecek, bu yüzden onu iletmek boşa harcanmış bir çaba gibi görünüyor. Ancak, bir Profil nesnesini , Ayarlar nesnesine (çözüm yerine sahte bir nesne kullanarak) gerekmeden kendi başına test etmeye ilk defa başladığınızda ihtiyaç çok belirgindir.
JPhi1618,

2
@ JPhi1618 "DI Birim Sınaması için" seçeneğinin vurgulanması ile ilgili bir sorunun "neden Birim Sınamalarına neden ihtiyacım var" sorusuna yol açtığını düşünüyorum. Cevabınız sizin için bariz görünebilir ve faydaları kesinlikle oradadır, ancak yeni başlayan biri için "bu diğer karmaşık ses işini yapmak için bu karmaşık ses işini yapmanız gerekir" diyerek biraz bir kapatma. Bu yüzden şu anda yaptıklarına daha uygun olabilecek farklı avantajlardan bahsetmek iyidir.
IMSoP

1
@ user986730 - bu çok iyi bir nokta ve buradaki gerçek prensibin Enjeksiyonun tek bir teknik olduğu Bağımlılık İnversiyonu olduğunun altını çiziyor . Örneğin, C'de IOC kitaplığı kullanarak gerçekten bağımlılıklar enjekte edemezsiniz, ancak aynı etkiye sahip alaylarınız, vb. için farklı kaynak dosyaları ekleyerek derleme zamanında bunları ters çevirin.
Stephen Byrne

64

Bağımlılık Enjeksiyonu, kodunuzun test edilmesini kolaylaştırır.

Magento'nun PayPal entegrasyonunda yakalanması zor bir hatayı düzeltmekle görevlendirdiğimde bunu ilk elden öğrendim.

PayPal Magento’ya başarısız bir ödeme yapıldığını söylerken bir sorun ortaya çıkacaktı: Magento hatayı doğru bir şekilde kaydetmedi.

Potansiyel bir düzeltmeyi "manuel" olarak test etmek çok sıkıcı olacaktır: bir şekilde "Başarısız" PayPal bildirimini tetiklemeniz gerekir. Bir e-çek göndermeniz, iptal etmeniz ve hata yapmasını beklemeniz gerekir. Tek karakterli kod değişikliğini test etmek için 3+ gün demektir!

Neyse ki, bu işlevi geliştiren Magento çekirdek devlerinin akılda test edildiğini ve önemsiz hale getirmek için bir bağımlılık enjeksiyon modeli kullandığı anlaşılıyor. Bu, çalışmamızı bunun gibi basit bir test durumuyla doğrulamamızı sağlar:

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

DI modelinin birçok başka avantajı olduğuna eminim, ancak test edilebilirliği artırmak aklımdaki en büyük avantaj .

Bu sorunun çözümünü merak ediyorsanız, GitHub deposunu buradan inceleyin: https://github.com/bubbleupdev/BUCorefix_Paypalstatus


4
Bağımlılık enjeksiyonu kodun zor kodlanmış bağımlılıklardan daha kolay test edilmesini sağlar. Bağımlılıkları iş mantığından tamamen ortadan kaldırmak daha da iyidir.
Ant P,

1
Ve @AntP'nin önerdiği şeyi yapmanın en önemli yollarından biri parametrelendirmedir . Veritabanından gelen sonuçları, bir sayfa şablonunu doldurmak için kullanılan nesneye (genellikle "model" olarak bilinir) dönüştürmek için bir mantık, bir alımın bile olduğunu bilmemelidir; sadece bu nesneleri girdi olarak almak gerekiyor.
jpmc26

4
Gerçekten de @ jpmc26 - Bağımlılıkları enjekte etmek yerine etki alanınıza veri aktarmak için gerçekten süslü bir isim olan fonksiyonel çekirdek, zorunlu kabuk hakkında sıkıntıya girme eğilimindeyim . Etki alanı saf, birim alay olmadan test edilebilir ve daha sonra kalıcılık, mesajlaşma vb. Şeyleri uyarlayan bir kabuğa sarılır.
Ant P

1
Bence test edilebilirliğin tek odak noktası DI'nin benimsenmesi için zararlı olduğunu düşünüyorum. Sınamaya çok ihtiyaç duymadığını ya da zaten kontrol altında olduklarını düşünen insanlar için çekici değildir. DI olmadan temiz, tekrar kullanılabilir bir kod yazmanın neredeyse imkansız olduğunu savunuyorum. Testler faydalar listesinde oldukça düşüktür ve bu cevabın DI'nin faydalarıyla ilgili her soruda 1. veya 2. sırada yer alması hayal kırıklığı yaratıyor.
Carl Leth,

26

Neden (sorun ne?)

Neden bağımlılık enjeksiyonunu kullanmalıyım?

Bunun için bulduğum en iyi anımsatıcı " yeni yapıştırıcı " dır : newKodunuzda her kullandığınızda , bu kod o özel uygulamaya bağlıdır . Yapıcılarda art arda yeni kullanırsanız , belirli bir uygulama zinciri oluşturacaksınız . Ve onu inşa etmeden bir sınıfın örneğini "sahip olamayacağınız" için, o zinciri ayıramazsınız.

Örnek olarak, bir yarış arabası video oyunu yazdığınızı hayal edin. Her biri birer , 8 , Gamea RaceTrack, yaratan bir sınıfla başladınız . Şimdi farklı bir ivmeyle 4 ek istiyorsanız , belki hariç her sınıfı değiştirmeniz gerekecektir .CarsMotorCarsGame

Temizleyici kodu

Bu sadece daha temiz mimari / kod için mi

Evet .

Ancak, bu durumda çok daha iyi anlaşılamayabilir , çünkü bunun nasıl yapılacağına bir örnek . Gerçek avantaj, yalnızca birkaç sınıfın dahil olduğunu ve gösterilmesi daha zor olduğunu gösterir, ancak önceki örnekte DI kullandığınızı hayal edin. Bütün bunları yaratan kod şöyle görünebilir:

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

Bu 4 farklı vagonu eklemek şimdi bu satırları ekleyerek yapılabilir:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • Hayır değişiklikler RaceTrack, Game, Carveya Motorgerekliydi - biz herhangi bir yeni böcek var getirmemiştir% 100 emin olmak anlamına gelir!
  • Birkaç dosya arasında geçiş yapmak yerine, ekranınızdaki değişimin tamamını aynı anda görebilirsiniz. Bu, yaratma / kurulum / konfigürasyonun kendi sorumluluğu olarak görülmesinin bir sonucudur - bir motor yapmak için bir arabanın işi değildir.

Performansla ilgili önemli noktalar

veya bu bir bütün olarak performansı etkiler mi?

Hayır . Ama sana karşı tamamen dürüst olmak gerekirse, olabilir.

Bununla birlikte, bu durumda bile, o kadar gülünç derecede küçük bir miktar ki umursaya gerek yok. Gelecekte bir noktada, sen 5Mhz CPU ve 2MB RAM eşdeğer Tamagotchi için kod yazmak zorunda, o zaman belki sen belki bu umurumda gerekiyor.

Durumların% 99,999'unda * daha iyi bir performansa sahip olacaktır, çünkü hataları gidermek için daha az zaman harcadınız ve kaynak ağırlıklı algoritmalarınızı geliştirmek için daha fazla zaman harcadınız.

* tamamen yapılmış numara

Eklenen bilgiler: "kodlanmış"

Hata yapmayın, bu hala çok "kodlanmış" dır - sayılar doğrudan koda yazılır. Sabit kodlanmayan, bu değerleri bir metin dosyasına (örneğin JSON formatında) saklamak ve daha sonra bu dosyadan okumak gibi bir şey anlamına gelir.

Bunu yapmak için, bir dosyayı okumak ve ardından JSON'u ayrıştırmak için kod eklemeniz gerekir. Örneği tekrar düşünürseniz; DI olmayan sürümde, bir Carveya Motorşimdi bir dosyayı okumak zorundadır. Bu çok fazla mantıklı gibi gelmiyor.

DI versiyonunda, oyunu ayarlayan koda eklersiniz.


3
Reklam kodlanmış, kod ve config dosyası arasında gerçekten bu kadar fark yoktur. Dinamik olarak okumanıza rağmen uygulamayla birlikte verilen bir dosya bir kaynaktır. Değerleri koddan veri dosyalarına, json ya da 'config' biçiminde çekmek, değerler kullanıcı tarafından geçersiz kılınmadığı ya da çevreye bağlı olmadıkça hiçbir şey yapmaz.
Jan Hudec

3
Aslında bir Tamagotchi'yi bir Arduino'ya (16MHz, 2KB)
yaptım

@JanHudec Doğru. Aslında orada daha uzun bir açıklama yaptım, ancak daha kısa tutması ve DI ile olan ilişkisine odaklanması için çıkarmaya karar verdim. % 100 doğru olmayan daha çok şey var; Genel olarak cevap, DI'nin "noktasını" çok uzun sürmeden itmek için daha optimize edilmiştir. Ya da başka bir deyişle, DI ile başladığımda duymak istediğim şey budur.
R. Schmitz

17

Bağımlılık enjeksiyonundan hep şaşırdım. Sadece Java küreleri içinde var gözüküyordu, ancak bu küreler bundan büyük bir saygıyla bahsetti. Gördüğünüz gibi, kaosa düzen getirdiği söylenen en büyük Kalıplardan biriydi. Ancak örnekler her zaman kıvrımlı ve yapay, problemsiz bir hale geldi ve daha sonra kodu daha karmaşık hale getirerek çözüme çıkarıldı.

Bir dost Python devi bana bu bilgeliği verdiğinde daha anlamlıydı: sadece işlevlere argümanları geçmek. Bu neredeyse hiç bir kalıp değil; Eğer bir hatırlatma gibi daha edebilir , bağımsız değişken olarak bir şey isteyecek bile sen makul mantıklı bir değer kendiniz temin olabilirdi.

Öyleyse sorunuz kabaca eşittir "işlevim neden tartışmalı?" ve aynı cevapların çoğuna sahiptir, yani: Arayanın karar vermesine izin vermek için .

Elbette bunun bir bedeli var, çünkü şimdi arayanı bir karar vermesi için zorluyorsunuz (argümanı isteğe bağlı yapmazsanız) ve arayüz biraz daha karmaşık. Buna karşılık, esneklik kazanırsınız.

Öyleyse: Bu özel Settingtip / değeri özellikle kullanmanızın iyi bir nedeni var mı ? Arama kodunun farklı bir Settingtür / değer isteyebilmesi için iyi bir neden var mı ? (Unutma, testler koddur !)


2
Evet. IoC sonunda “Arayanın karar vermesine izin vermek” ile ilgili olduğunu anladığımda bana tıkladı. Bu IoC, bileşenin kontrolünün bileşenin yazarından bileşenin kullanıcısına kaydırıldığı anlamına gelir. Ve bu noktada, benden daha akıllı olduğunu düşünen yazılımlarla zaten yeterince uğraştım, hemen DI yaklaşımıyla satıldım.
Joker_vD

1
“Sadece işlevlere argümanları iletmek. Neredeyse hiç bir model değil” Bu, duyduğum DI'nin tek iyi veya hatta anlaşılır bir açıklamasıdır (çoğu insan ne olduğu hakkında konuşmaz , faydaları hakkında bile konuşmaz ). Daha sonra, gerçekten basit bir fikir olduğunda anlamak için daha fazla soru gerektiren “kontrolün tersine çevrilmesi” ve “gevşek bağlanma” gibi karmaşık bir jargon kullanıyorlar.
addison

7

Verdiğiniz örnek, klasik anlamda bağımlılık enjeksiyonu değil. Bağımlılık enjeksiyonu, genellikle yeni oluşturulmuş bir nesnede bir alana bir değer ayarlamak için, bir kurucudaki nesnelerin geçirilmesi veya nesnenin oluşturulduktan hemen sonra "ayarlayıcı enjeksiyon" kullanılmasıyla ifade edilir.

Örneğiniz bir nesneyi bir example yöntemine argüman olarak iletir. Bu örnek yöntemi daha sonra o nesne üzerindeki bir alanı değiştirir. Bağımlılık enjeksiyonu? Hayır. Kapsülleme ve veri gizliliğinin kırılması? Kesinlikle!

Şimdi, eğer kod şöyle olsaydı:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

Sonra bağımlılık enjeksiyonu kullandığınızı söyleyebilirim. Kayda değer fark, Settingsyapıcıya iletilen bir Profilenesne veya bir nesnedir.

Ayarlar nesnesinin inşa edilmesi pahalı veya karmaşıksa veya Ayarlar, çalışma zamanı davranışını değiştirmek için birden fazla somut uygulamanın bulunduğu bir arayüz veya soyut bir sınıfsa faydalıdır.

Bir yöntemi çağırmak yerine doğrudan Ayarlar nesnesindeki bir alana eriştiğinizden, bağımlılık enjeksiyonunun faydalarından biri olan Polimorfizmden yararlanamazsınız.

Bir Profil için Ayarlar, o profile özgüdür. Bu durumda aşağıdakilerden birini yaparım:

  1. Profil yapıcısının içindeki Settings nesnesini örnekle

  2. Yapıcıdaki Settings nesnesini iletin ve Profile uygulanan alanların üzerine kopyalayın

Dürüst olmak gerekirse, Ayarlar nesnesini içeri sokup sonra Ayarlar nesnesinin deactivateProfileiç alanını değiştirmek bir kod kokusudur. Ayarlar nesnesi, iç alanlarını değiştiren tek nesne olmalıdır.


5
The example you give is not dependency injection in the classical sense.- Fark etmez. OO insanlarının beyinde nesneleri var, ama hala bir şeye
Robert Harvey,

1
Klasik anlamda ” hakkında konuştuğunuzda , @RobertHarvey'in dediği gibi, tamamen OO terimleri ile konuşuyorsunuz. Örneğin, işlevsel programlamada, bir işlevi diğerine enjekte etmek (yüksek dereceli fonksiyonlar), paradigmanın klasik bağımlılık enjeksiyonunun örneğidir.
David Arno

3
@RobertHarvey: "Bağımlılık enjeksiyonuyla" çok fazla özgürlük alıyordum galiba. İnsanların en çok terimi düşündüğü ve terimi kullandığı yer, nesne inşası sırasında ya da pastalın enjeksiyonundan hemen sonra "enjekte edilen" bir nesne üzerindeki alanlara atıfta bulunur.
Greg Burghardt

@DavidArno: Evet, haklısın. OP, soru yönelimli bir PHP kod parçacığına sahip görünüyor, bu yüzden sadece bu konuda yanıt veriyorum ve fonksiyonel programlamayı ele almıyorum --- PHP ile aynı soruyu işlevsel programlama açısından sorabildiğiniz halde.
Greg Burghardt

7

Bu partiye geç geldiğimi biliyorum ama önemli bir noktanın kaçırıldığını hissediyorum.

Neden bunu yapmalıyım:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Yapmamalısın. Fakat Bağımlılık Enjeksiyonu kötü bir fikir olduğu için değil. Çünkü bu yanlış yapıyor.

Kod kullanarak bu şeylere bakalım. Bunu yapacağız:

$profile = new Profile();
$profile->deactivateProfile($setting);

bundan aynı şeyi aldığımızda:

$setting->isActive = false; // Deactivate profile

Tabii ki zaman kaybı gibi görünüyor. Bu şekilde yaptığın zaman. Bağımlılık Enjeksiyonunun en iyi kullanımı bu değildir. Bir sınıfın en iyi kullanımı bile değil.

Şimdi, peki ya bunun yerine:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

Ve şimdi, aslında gerçekten değiştiği hakkında hiçbir şey bilmek zorunda kalmadan application, aktifleştirmek ve devre dışı bırakmak ücretsizdir . Neden bu iyi? Ayarını değiştirmeniz gerekirse. Bu değişikliklerle duvarlar örülür, böylece bir şeye dokunur dokunmaz her şeyin kopuşunu izlemenize gerek kalmadan güvenli bir alanda boşuna gidebilirsiniz.profilesettingapplication

Bu , davranış ilkesinden ayrı bir yapı izler . Buradaki DI modeli basit. İhtiyacınız olan her şeyi mümkün olan en düşük seviyede kurun, birleştirin, sonra tüm davranışları tek bir çağrı ile başlatmaya başlayın.

Sonuç, neyin neye neyin bağlandığına karar vermek için ayrı bir yeriniz ve neyin neye neyin söylediğini yönetmek için farklı bir yerinizdir.

Bunu zaman içinde sürdürmeniz gereken bir şey üzerinde deneyin ve işe yaramadığını görün.


6

Bir müşteri olarak, bir tamirciyi arabanıza bir şeyler yapacak bir araç kiraladığınızda, o tamircinin sıfırdan bir araba yapmasını ve sonra onunla çalışmasını mı beklersiniz? Hayır, tamirciye çalışmasını istediğiniz aracı verin .

Garaj sahibi olarak, bir tamirciye bir arabaya bir şey yapması talimatını verdiğinizde, tamirciden kendi tornavida / İngiliz anahtarı / otomobil parçalarını yaratmasını bekler misiniz? Hayır, tamirciye kullanması gereken parçaları / araçları sağlıyorsunuz

Bunu neden yapıyoruz? Peki bunun hakkında düşün. Tamirciniz olmak için birini kiralamak isteyen bir garaj sahibisiniz. Onlara bir tamirci olmayı öğreteceksiniz (= kodu yazacaksınız).

Daha kolay ne olacak:

  • Bir tamirci bir tornavida kullanarak bir arabaya bir spoiler nasıl bağlanır öğretin.
  • Bir araba oluşturmak için bir tamirci öğretin, bir spoyler oluşturun, bir tornavida yaratın ve ardından yeni oluşturulan spoyleri yeni oluşturulan tornavidayla yeni oluşturulan arabaya takın.

Tamircinizin sıfırdan her şeyi yaratmaması için büyük avantajlar vardır:

  • Açıkçası, eğer tamircinize mevcut alet ve parçaları tedarik ediyorsanız, eğitim (= geliştirme) önemli ölçüde kısalır.
  • Aynı tamircinin aynı işi birden fazla kez yapması gerekiyorsa, eskisini dışarı atmak ve yeni bir tane oluşturmak yerine tornavidayı tekrar kullandığından emin olabilirsiniz.
  • Ek olarak, her şeyi yaratmayı öğrenen bir tamircinin çok daha fazla uzman olması ve dolayısıyla daha yüksek bir ücret beklemesi gerekecektir. Buradaki kodlama benzetmesi, birçok sorumluluğu olan bir sınıfın, kesin olarak tanımlanmış tek bir sorumluluğu olan bir sınıftan daha sürdürmesi daha zor olmasıdır.
  • Ek olarak, yeni buluşlar piyasaya sürüldüğünde ve spoiler artık plastik yerine karbondan yapılıyor; Uzman teknisyeni yeniden eğitmek zorunda kalacaksınız (= yeniden geliştirme). Ancak "basit" tamirciniz, spoiler aynı şekilde tutturulduğu sürece yeniden eğitilmek zorunda kalmayacaktır.
  • Kendi ürettikleri bir arabaya güvenmeyen bir tamirciye sahip olmak, alabilecekleri herhangi bir aracı kullanabilecek bir tamirciniz olduğu anlamına gelir . Tamircinin eğitimi sırasında henüz bulunmamış otomobiller dahil. Bununla birlikte, uzman teknisyeniniz, eğitimlerinden sonra oluşturulan yeni arabalar üretemez.

Uzman teknisyeni işe alır ve eğitirseniz, daha fazla masrafa sahip, basit bir iş olması gereken şeyi yapmak için daha fazla zaman alan ve birçok sorumluluğunun birisine ihtiyaç duyulduğunda sürekli olarak yeniden eğitilmesi gerekecektir. güncellenecek.

Geliştirme analojisi, kodlanmış bağımlılığı olan sınıfları kullanırsanız, nesnenin yeni bir versiyonunun ( Settingssizin durumunuzda) ne zaman yaratıldığı zaman sürekli iyileştirme / değişiklik gerektirecek sınıfları sürdürmek için zorlanacağınızdır; Sınıfın farklı türden Settingsnesneler yaratma kabiliyetine sahip olması için iç mantık geliştirmeliyim .

Ayrıca, sınıfınızı her kim tüketirse, sınıftan geçmek istediği herhangi bir nesneyi geçebilmenin aksine , sınıftan doğru nesneyi oluşturmasını istemek zorunda kalacaktır. Bu, tüketiciden sınıftan doğru aracı nasıl oluşturmasını istediğini çözmesi için ek gelişme anlamına gelir.SettingsSettings


Evet, bağımlılık inversiyonu, bağımlılığı kodlamak yerine yazmak için biraz daha çaba gösterir. Evet, daha fazla yazmak zorunda olmak can sıkıcı.

Ancak bu, değişmez değerlerin kodlanmasını seçmekle aynı argümandır, çünkü "değişkenleri bildirmek daha fazla çaba harcar". Teknik olarak doğru, ancak profesyonellerin eksileri birkaç büyüklük sırasına göre ağır basmaktadır .

Uygulamanın ilk sürümünü oluşturduğunuzda bağımlılık inversiyonunun yararı yaşanmaz. Bağımlılık inversiyonunun faydası, bu ilk sürümü değiştirmeniz veya uzatmanız gerektiğinde yaşanır. Ve ilk seferinde doğru yapacağınızı ve kodu genişletmeye / değiştirmeye ihtiyaç duymayacağınızı düşünerek kendinizi kandırma. Bir şeyleri değiştirmek zorunda kalacaksın.


Bu, performansı bir bütün olarak etkiler mi?

Bu, uygulamanın çalışma zamanının performansını etkilemez. Ama kitlesel etkiler geliştirme süresi (ve dolayısıyla performans) geliştirici .


2
Yorumunuzu kaldırırsanız, " Küçük bir yorum olarak, sorunuz, bağımlılık enjeksiyonuna değil, bağımlılık inversiyonuna odaklanır. Enjeksiyon, inversiyon yapmanın bir yoludur, ancak tek yol bu değildir. ", Bu mükemmel bir cevap olacaktır. Bağımlılık inversiyonu enjeksiyon veya yer belirleyici / küre ile sağlanabilir. Sorunun örnekleri enjeksiyonla ilgilidir. Bu yüzden soru , bağımlılık enjeksiyonuyla (bağımlılık inversiyonunun yanı sıra) ilgilidir.
David Arno

12
Bence bütün araba işi biraz karışık ve kafa karıştırıcı
Ewan

4
@Flater, problemin bir kısmı gerçekten bağımlılık enjeksiyonu, bağımlılık inversiyonu ile kontrol inversiyonu arasındaki farkın ne olduğu konusunda hemfikir değil gibi görünüyor. Kesin olan bir şey olsa da, bir yöntem veya kurucuya bağımlılık enjekte etmek için kesinlikle bir "konteyner" gerekli değildir. Saf (veya fakir erkeğin) DI özellikle manuel bağımlılık enjeksiyonunu tanımlar. Kişisel olarak kullandığım tek bağımlılık enjeksiyonu bu, kaplarla ilgili "sihir" ten hoşlanmam.
David Arno

1
@Flater Örnekteki problem, neredeyse kesinlikle herhangi bir problemi bu şekilde kodlamayacak olmanızdır. Bu nedenle doğal değildir, zorlanır ve cevaplardan daha fazla soru ekler. Bu, yanlış göstermek ve şaşırtmak için göstermekten daha muhtemeldir.
jpmc26

2
@gbjbaanb: Cevabım hiçbir zaman uzaktan polimorfizme bile dalmıyor. Miras, arayüz uygulaması veya türlerin yukarı / aşağı yayınlanmasına benzeyen herhangi bir şey yoktur. Cevabı tamamen yanlış okuyorsun.
Flast

0

Tüm kalıplarda olduğu gibi, şişirilmiş tasarımlardan kaçınmak için "neden" sormak çok geçerlidir.

Bağımlılık enjeksiyonu için bu, OOP tasarımının iki, tartışmalı, en önemli yönünü düşünerek kolayca görülebilir ...

Düşük kavrama

Bilgisayar programlamasında kuplaj :

Yazılım mühendisliğinde, birleştirme, yazılım modülleri arasındaki karşılıklı bağımlılık derecesidir; iki rutinin veya modülün ne kadar yakından bağlandığının bir ölçütü; modüller arasındaki ilişkilerin gücü.

Düşük kavrama elde etmek istiyorsunuz . Güçlü bir şekilde birbirine bağlı iki şey, birini değiştirirseniz, diğerini de değiştirmeniz gerektiği anlamına gelir. Birindeki hatalar veya kısıtlamalar, diğerinde hata / kısıtlamaları doğuracaktır; ve bunun gibi.

Bir sınıf diğerlerinin nesnelerini somutlaştırır, çok güçlü bir eşleşmedir, çünkü birinin diğerinin varlığını bilmesi gerekir; Bunun nasıl başlatılacağını bilmesi gerekir (hangi kurucunun ihtiyacı olduğunu tartışır) ve bu argümanları yapıcı çağırırken erişilebilir olması gerekir. Ayrıca, dilin açık bir şekilde yapılandırılmasının gerekip gerekmediğine bağlı olarak (C ++), bu başka komplikasyonlara neden olacaktır. Yeni sınıflar tanıtırsanız (yani bir NextSettingsveya her neyse), orijinal sınıfa geri dönmeniz ve yapıcılarına daha fazla çağrı eklemeniz gerekir.

Yüksek uyum

Uyum :

Bilgisayar programlamasında, uyum bir modülün içindeki elemanların birbirine ait olduğu dereceyi belirtir.

Bu madalyonun diğer yüzü. Bir kod birimine bakarsanız (bir yöntem, bir sınıf, bir paket vb.), Birim içindeki tüm kodların mümkün olduğunca az sorumluluğu olmasını istersiniz.

Bunun temel bir örneği MVC paterni olacaktır: etki alanı modelini görünümden (GUI) ve bunları birbirine bağlayan bir kontrol katmanını açıkça ayırırsınız.

Bu, birçok farklı şey yapan büyük parçaların bulunduğu kod şişmesini önler; bir kısmını değiştirmek istiyorsanız, diğer tüm özellikleri de takip etmelisiniz; ve hızlı bir şekilde kendinizi çıkmanın çok zor olduğu bir deliğe programlıyorsunuz.

Bağımlılık enjeksiyonuyla, DI'nizi uygulayan hangi sınıflara (veya yapılandırma dosyalarına) bağımlılıkların oluşturulmasını veya izlemesini sağlarsınız. Diğer sınıflar tam olarak ne olup bittiğini umursamayacaklar - bazı genel arayüzlerle çalışacaklar ve gerçek uygulamanın ne olduğu hakkında hiçbir fikirleri olmayacak , yani diğer şeylerden sorumlu olmadıkları anlamına gelecekler .


-1

Problemleri çözmek için çözmede iyi oldukları problemleri çözmek için teknikleri kullanmalısınız. Bağımlılık inversiyonu ve enjeksiyonu farklı değildir.

Bağımlılık inversiyonu veya enjeksiyonu, kodunuzun bir yöntemin hangi uygulamada çalıştırma sırasında çağrılacağına karar vermesini sağlayan bir tekniktir. Bu geç bağlanma avantajlarını en üst düzeye çıkarır. Teknik, dil, örnek olmayan işlevlerin çalışma zamanı değişimini desteklemediğinde gereklidir. Örneğin, Java, farklı bir uygulamaya yapılan çağrılarla statik bir yönteme yapılan çağrıları değiştirme mekanizmasından yoksundur; işlev çağrısını değiştirmek için gereken tek şey, adı farklı bir işleve bağlamaktır (işlevi tutan değişkeni yeniden atayın).

Neden fonksiyonun uygulanmasını değiştirmek isteyelim? İki ana sebep var:

  • Sahte sınama amacıyla kullanmak istiyoruz. Bu, aslında veritabanına bağlanmadan, veritabanı alımına bağlı bir sınıfı sınamamızı sağlar.
  • Birden fazla uygulamayı desteklememiz gerekiyor. Örneğin, hem MySQL hem de PostgreSQL veritabanlarını destekleyen bir sistem kurmamız gerekebilir.

Ayrıca kontrol konteynırlarının inversiyonunu not etmek isteyebilirsiniz. Bu, sözde kod gibi görünen devasa, karışık yapı ağaçlarından kaçınmanıza yardımcı olacak bir tekniktir:

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

Sınıflarınızı kaydetmenizi sağlar ve ardından sizin için inşaatı yapar:

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

Kayıtlı sınıfların vatansız tekil olmak olması en basitidir .

Dikkatli kelime

Bağımlılık inversiyon gerektiğini unutmayın değil senin olmak go-mantık koparma için cevap. Bunun yerine parametreleştirme kullanmak için fırsatları arayın . Örneğin bu sözde kod yöntemini göz önünde bulundurun:

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

Bu yöntemin bazı bölümleri için bağımlılık inversiyonunu kullanabiliriz:

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

Ama yapmamalıyız, en azından tamamen değil. Bununla birlikte durum bilgisi olan bir sınıf yarattığımıza dikkat edin Querier. Şimdi esas olarak bazı küresel bağlantı nesnelerine referansta bulunuyor. Bu, programın genel durumunu anlamada zorluk ve farklı sınıfların birbirleriyle nasıl koordine edilmesi gibi sorunlar yaratır. Ayrıca, ortalama mantığı test etmek istiyorsak, sorgulayıcıyı veya bağlantıyı taklit etmemiz gerektiğine dikkat edin. Ayrıca Parametreyi arttırmak için daha iyi bir yaklaşım olacaktır :

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

Bağlantı, operasyondan bir bütün olarak sorumlu olan ve bu çıktıyla ne yapacağını bilen daha da yüksek bir seviyede yönetilecekti.

Artık ortalama mantığı sorgulamadan tamamen bağımsız olarak test edebiliyoruz ve daha geniş bir yelpazede kullanabiliyoruz. MyQuerierVe Averagernesnelere ihtiyaç duyup duymadığımızı bile sorgulayabiliriz ve belki de cevabı, birim testini yapmak niyetinde değilsek ve birim testini StuffDoeryapmamak, StuffDoermakul bir şekilde veritabanına çok sıkı bir şekilde bağlandığından dolayı makul olamayacağıdır. Entegrasyon testlerinin onu kapsamasına izin vermek daha mantıklı olabilir. Bu durumda, ince yapım fetchAboveMinve averageDatastatik yöntemlerde olabiliriz.


2
" Bağımlılık enjeksiyonu, büyük, karışık inşaat ağaçlarından kaçınmanıza yardımcı olacak bir tekniktir ... ". Bu iddiadan sonraki ilk örneğiniz, saf ya da yoksul adamın bağımlılık enjeksiyonunun bir örneğidir. İkincisi, bu bağımlılıkların enjeksiyonunu "otomatikleştirmek" için bir IoC kabı kullanmanın bir örneğidir. Her ikisi de eylemde bağımlılık enjeksiyonunun örnekleridir.
David Arno

@DavidArno Evet, haklısın. Terminolojiyi ayarladım.
jpmc26

Uygulamayı değiştirmek ya da en azından uygulamanın değişebileceğini varsayarak kodu tasarlamak için üçüncü bir ana sebep var: geliştiriciyi gevşek bir bağlantıya sahip olmaya teşvik ediyor ve bir zamanda yeni bir uygulamanın uygulanması durumunda değişmesi zor olacak kodun yazılmasını engelliyor gelecek. Ve bu bazı projelerde öncelik teşkil etmeyebilir (örneğin, uygulamanın ilk sürümünden sonra asla tekrar ziyaret edilmeyeceğini bilerek), diğerlerinde olacaktır (örneğin, şirket iş modelinin özellikle uygulamalarının uzatılmasını / uzatılmasını teklif etmeye çalıştığı yerlerde) ).
flater

@Flater Bağımlılık enjeksiyonu hala güçlü bağlantıya neden olur. Mantığı, belirli bir arayüze bağlar ve söz konusu kodun, bu arayüzün ne yaptığını bilmesini gerektirir. Bir DB'den alınan sonuçları dönüştüren kodu düşünün. Onları ayırmak için DI kullanırsam, kodun hala bir DB alımının olacağını bilmesi ve onu çağırması gerekir. Daha iyi bir çözüm, dönüşüm kodunun bir alımın olduğunu bile bilmemesidir . Bunu yapmanın tek yolu , getirme sonuçlarının , alıcıya enjekte etmek yerine arayan tarafından iletilmesidir.
jpmc26 15:18

1
@ jpmc26 Ancak (yorumunuz) örneğinde, veritabanının başlamak için bir bağımlılık olması gerekmedi. Elbette bağımlılıklardan kaçınmak gevşek bağlanma için daha iyidir, ancak DI gerekli olan bağımlılıkları uygulamaya odaklanır.
Flater
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.