Bağımlılık enjeksiyon çerçeveleri için argümanlardan birini sorgulamak: Nesne grafiği oluşturmak neden zor?


13

Google Guice gibi bağımlılık enjeksiyon çerçeveleri kullanımları için aşağıdaki motivasyonu sağlar ( kaynak ):

Bir nesneyi oluşturmak için önce bağımlılıklarını oluşturursunuz. Ancak her bağımlılığı inşa etmek için bağımlılıklarına ihtiyacınız var vb. Bu nedenle, bir nesne oluşturduğunuzda, gerçekten bir nesne grafiği oluşturmanız gerekir.

Elle nesne grafikleri oluşturmak emek yoğundur (...) ve testi zorlaştırır.

Ancak bu argümanı satın almıyorum: Bağımlılık enjeksiyon çerçevesi olmadan bile, hem örneklemesi kolay hem de test edilmesi kolay sınıflar yazabilirim. Örneğin, Guice motivasyon sayfasındaki örnek şu şekilde yeniden yazılabilir:

class BillingService
{
    private final CreditCardProcessor processor;
    private final TransactionLog transactionLog;

    // constructor for tests, taking all collaborators as parameters
    BillingService(CreditCardProcessor processor, TransactionLog transactionLog)
    {
        this.processor = processor;
        this.transactionLog = transactionLog;
    }

    // constructor for production, calling the (productive) constructors of the collaborators
    public BillingService()
    {
        this(new PaypalCreditCardProcessor(), new DatabaseTransactionLog());
    }

    public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard)
    {
        ...
    }
}

Bağımlılık enjeksiyon çerçeveleri için ( bu sorunun kapsamı dışındadır !) Başka argümanlar olabilir , ancak test edilebilir nesne grafiklerinin kolay oluşturulması bunlardan biri değildir, değil mi?


1
Sıklıkla görmezden gelen bağımlılık enjeksiyonunun başka bir avantajının sizi hangi nesnelerin bağımlı olduğunu bilmeye zorlaması olduğunu düşünüyorum . Nesnelerde sihirli bir şey görünmez, ya açıkça işaretlenmiş niteliklerle enjekte edersiniz ya da doğrudan yapıcı aracılığıyla enjekte edersiniz. Kodunuzu nasıl daha test edilebilir hale getirdiğinden bahsetmiyorum bile.
Benjamin Gruenbaum

Bağımlılık enjeksiyonunun diğer avantajlarını aramıyorum unutmayın - Ben sadece "bağımlılık enjeksiyon olmadan nesne örnekleme zor" argümanı anlamak
ilgimi çekiyor

Bu yanıt , nesne grafikleriniz için neden bir tür kapsayıcı istediğinize dair çok iyi bir örnek sağlıyor gibi görünüyor (kısacası, sadece 3 nesne kullanarak yeterince büyük düşünmüyorsunuz).
Izkata

@Izkata Hayır, bu bir boyut meselesi değil. Açıklanan yaklaşımla, kod , o yazının sadece olurdu new ShippingService().
oberlies

@oberlies Hala; daha sonra, tek bir konum yerine bu ShippingService için hangi sınıfların kullanıldığını anlamak için 9 sınıf tanımında kazmaya gitmeniz gerekir
Izkata

Yanıtlar:


12

Bağımlılık enjeksiyonu yapmanın en iyi yolu hakkında eski, devam etmekte olan bir tartışma var.

  • İlkbahar kesimi düz bir nesneyi başlattı ve sonra ayarlayıcı yöntemlere rağmen bağımlılıklar enjekte etti.

  • Ancak daha sonra büyük bir olasılık, yapıcı parametreleri aracılığıyla bağımlılıkların enjekte edilmesinin, bunu yapmanın doğru yolu olduğu konusunda ısrar etti.

  • Son zamanlarda, yansıma kullanmak daha yaygın hale geldikçe, özel üyelerin değerlerini belirleyiciler veya kurucu argümanları olmadan doğrudan ayarlamak öfke haline geldi.

Böylece ilk kurucunuz bağımlılık enjeksiyonuna ikinci yaklaşımla tutarlıdır. Test için enjeksiyon enjekte etmek gibi güzel şeyler yapmanıza izin verir.

Ancak bağımsız değişken yapıcısının bu sorunu vardır. Uygulama sınıflarını somutlaştırdığı için PaypalCreditCardProcessorve DatabaseTransactionLogPayPal ve Veritabanına zor, derleme zamanı bağımlılığı yaratır. Tüm bağımlılık ağacının doğru bir şekilde oluşturulması ve yapılandırılması sorumluluk alır.

  • PayPay işlemcisinin gerçekten karmaşık bir alt sistem olduğunu ve ayrıca birçok destek kütüphanesini çektiğini düşünün. Bu uygulama sınıfına derleme zamanı bağımlılığı oluşturarak, tüm bağımlılık ağacına kırılmaz bir bağlantı oluşturursunuz. Nesne grafiğinizin karmaşıklığı az önce, belki iki büyüklükte bir artış gösterdi.

  • Bağımlılık ağacındaki bu öğelerin birçoğu şeffaf olacak, ancak birçoğunun da somutlaştırılması gerekecek. Oranlar, sadece bir örnek oluşturamayacaksınız PaypalCreditCardProcessor.

  • Örneklemeye ek olarak, nesnelerin her biri yapılandırmadan uygulanan özelliklere ihtiyaç duyacaktır.

Yalnızca arayüze bağımlılığınız varsa ve harici bir fabrikanın bağımlılığı oluşturmasına ve enjekte etmesine izin verirseniz, PayPal bağımlılık ağacının tamamını kesersiniz ve kodunuzun karmaşıklığı arayüzde durur.

Yapılandırmada uygulama sınıflarını belirtmek (yani derleme zamanı yerine çalışma zamanında) belirtmek veya çevreye (test, entegrasyon, üretim) göre değişen daha dinamik bağımlılık özelliklerine sahip olmak gibi başka avantajlar da vardır.

Örneğin, PayPal İşlemcinin 3 bağımlı nesnesi olduğunu ve bu bağımlılıkların her birinin iki tane daha olduğunu varsayalım. Ve tüm bu nesnelerin özellikleri yapılandırmadan almak zorunda. Olduğu gibi kod, tüm bunları oluşturma, özelliklerden yapılandırma vb. Vb. Ayarlama sorumluluğunu üstlenir - DI çerçevesinin ilgileneceği tüm endişeler.

İlk başta bir DI çerçevesi kullanarak kendinizi koruduğunuz şey belli olmayabilir, ancak zamanla toplanır ve ağrılı bir şekilde ortaya çıkar. (lol ben zor yoldan yapmaya çalışıyorum deneyimi konuşuyorum)

...

Uygulamada, gerçekten küçük bir program için bile, bir DI tarzında yazıyorum ve sınıfları uygulama / fabrika çiftlerine ayırıyorum. Yani eğer Spring gibi bir DI çerçevesi kullanmıyorsam, basit fabrika sınıflarını bir araya getiriyorum.

Bu, sınıfımın sadece bir şey yapabilmesi için endişelerin ayrılmasını sağlar ve fabrika sınıfı, bir şeyler oluşturma ve yapılandırma sorumluluğunu üstlenir.

Gerekli bir yaklaşım değil, FWIW

...

Daha genel olarak, DI / arayüz modeli iki şey yaparak kodunuzun karmaşıklığını azaltır:

  • akış aşağı bağımlılıkların arayüzlere soyutlanması

  • akış yukarı bağımlılıkları kodunuzdan ve bir tür kapsayıcıya "kaldırmak"

Bunun üzerine, nesne örnekleme ve yapılandırma oldukça tanıdık bir görev olduğundan, DI çerçevesi standart gösterim ve yansıma gibi hileler kullanarak birçok ölçek ekonomisine ulaşabilir. Aynı endişeleri sınıfların etrafına dağıtmak, birinin düşündüğünden çok daha fazla dağınıklığa neden olur.


Kod, tüm bunları oluşturma sorumluluğunu üstlenecektir - Bir sınıf yalnızca ortak çalışan uygulamalarının ne olduğunu bilmenin sorumluluğunu üstlenir. Bu ortak çalışanların sırayla neye ihtiyacı olduğunu bilmek gerekmez. Yani bu o kadar da değil.
oberlies

1
Ancak PayPalProcessor yapıcısında 2 argüman varsa, bunlar nereden gelirdi?
Rob

Öyle değil. Tıpkı BillingService gibi PayPalProcessor da kendisine ihtiyaç duyduğu ortak çalışanları oluşturan sıfır değişkenli bir kurucuya sahiptir.
oberlies

aşağı akım bağımlılıklarını arabirimlere soyutlama - Örnek kod bunu da yapar. İşlemci alanı, uygulama türünde değil, CreditCardProcessor türündedir.
oberlies

2
@oberlies: Kod örneğiniz, yapıcı bağımsız değişken stili ve sıfır değişkenli yapılandırılabilir stil olmak üzere iki stil arasında yer alır. Yanılgı, sıfır değişkenlerin inşa edilebilirliğinin her zaman mümkün olmamasıdır. DI veya Factory'nin güzelliği, nesnelerin bir şekilde sıfır değişkenli bir kurucuya benzeyen bir şekilde oluşturulmasına izin vermesidir.
rwong

4
  1. Havuzun sığ ucunda yüzerken, her şey "kolay ve rahat". Bir düzine kadar nesneyi geçtikten sonra artık uygun değildir.
  2. Örneğinizde, faturalandırma işleminizi sonsuza dek ve bir gün boyunca PayPal'a bağladınız. Farklı bir kredi kartı işlemcisi kullanmak istediğinizi varsayalım? Ağda kısıtlanmış özel bir kredi kartı işlemcisi oluşturmak istediğinizi varsayalım? Veya kredi kartı numarası işlemeyi test etmeniz mi gerekiyor? Taşınabilir olmayan bir kod oluşturdunuz: "bir kez yazın, yalnızca bir kez kullanın, çünkü tasarlandığı nesne grafiğine bağlıdır."

Nesne grafiğinizi işlemin başlarında bağlayarak, yani kodun içine bağlayarak, hem sözleşmenin hem de uygulamanın mevcut olmasını gerektirir. Başka biri (belki siz bile) bu kodu biraz farklı bir amaç için kullanmak isterse, tüm nesne grafiğini yeniden hesaplamalı ve yeniden uygulamalıdır.

DI çerçeveleri, bir grup bileşeni almanıza ve bunları çalışma zamanında birbirine bağlamanıza olanak tanır. Bu, sistemi birbirlerinin uygulamaları yerine birbirlerinin arayüzlerinde çalışan bir dizi modülden oluşan "modüler" hale getirir.


Argümanınızı takip ettiğimde, DI'nin mantığı "elle bir nesne grafiği oluşturmak kolay olmalı, ancak bakımı zor olan koda yol açıyor" olmalıdır. Ancak iddia "elle bir nesne grafiği oluşturmak zor" oldu ve ben sadece bu iddia destekleyen argümanlar arıyorum. Bu yüzden yayının soruma cevap vermiyor.
oberlies

1
Sanırım soruyu " bir nesne grafiği oluşturmak ..." a indirirseniz, o zaman bu basittir. Demek istediğim, DI hiçbir zaman tek bir nesne grafiği ile uğraşmadığınız sorunu ele alıyor ; onların bir ailesi ile anlaşıyorsunuz.
BobDalgleish

Aslında, ortak çalışanlar paylaşılıyorsa , yalnızca bir nesne grafiği oluşturmak zaten zor olabilir .
oberlies

4

"Kendi işbirlikçilerimi örnekle" yaklaşımı bağımlılık ağaçları için işe yarayabilir , ancak genel olarak yönlendirilmiş asiklik grafikler (DAG) olan bağımlılık grafikleri için kesinlikle iyi çalışmaz . Bir bağımlılık DAG'sında, birden fazla düğüm aynı düğümü gösterebilir - bu, iki nesnenin ortak çalışanla aynı nesneyi kullandığı anlamına gelir. Bu dava aslında soruda açıklanan yaklaşımla oluşturulamaz.

Ortak çalışanlardan bazılarının (veya ortak çalışanın ortak çalışanlarının) belirli bir nesneyi paylaşması gerekiyorsa, bu nesneyi somutlaştırmam ve ortak çalışanlarıma iletmem gerekir. Bu yüzden aslında doğrudan işbirlikçilerimden daha fazlasını bilmem gerekecekti ve bu açık bir şekilde ölçeklenmiyor.


1
Ancak bağımlılık ağacı , grafik teorisi anlamında bir ağaç olmalıdır . Aksi takdirde, basit bir şekilde tatmin edilemez bir döngüye sahipsiniz.
Xion

1
@Xion Bağımlılık grafiği yönlendirilmiş bir asiklik grafik olmalıdır. Ağaçlarda olduğu gibi iki düğüm arasında tam olarak bir yol bulunmasına gerek yoktur.
oberlies

@Xion: Mutlaka değil. UnitOfWorkTekil DbContextve çoklu depoları olan bir düşünün . Bu depoların tümü aynı DbContextnesneyi kullanmalıdır . OP'nin önerdiği "kendi kendine örnekleme" ile bunu yapmak imkansız hale gelir.
flater

1

Google Guice'i kullanmadım, ancak .Net'teki eski eski N katmanı uygulamalarını, şeyleri çözmek için Bağımlılık Enjeksiyonuna bağımlı Soğan Katmanı gibi IoC mimarilerine taşımak için çok zaman aldım.

Neden Bağımlılık Enjeksiyonu?

Bağımlılık Enjeksiyonunun amacı aslında test edilebilirlik için değildir, aslında sıkıca bağlanmış uygulamaları almak ve kaplini mümkün olduğunca gevşetmek. (Kodunuzun uygun birim testi için uyarlanmasını çok daha kolay hale getirme ürünü tarafından arzu edilen)

Neden kuplaj konusunda endişeleneyim ki?

Bağlama veya sıkı bağımlılıklar çok tehlikeli bir şey olabilir. (Özellikle derlenmiş dillerde) Bu durumlarda, çok nadiren kullanılan ve etkin bir şekilde tüm uygulamayı çevrimdışına alan bir sorunu olan bir kütüphane, dll, vb. Olabilir. (Önemsiz bir parçanın bir sorunu olduğu için tüm uygulamanız ölür ... bu kötü ... GERÇEKTEN kötü) Şimdi şeyleri ayırdığınızda, uygulamanızı ayarlayabilir, böylece DLL veya Kütüphane tamamen eksik olsa bile çalıştırabilirsiniz! Bu kütüphaneye veya DLL'e ihtiyaç duyan tek bir parçanın çalışmadığından emin olun, ancak uygulamanın geri kalanı mümkün olduğunca mutlu chugs.

Doğru test için neden Bağımlılık Enjeksiyonuna ihtiyacım var?

Gerçekten sadece gevşek bağlı kod istiyorsanız, Bağımlılık Enjeksiyonu bunu mümkün kılar. IoC olmadan bir şeyler gevşekçe birleştirebilirsiniz, ancak genellikle daha fazla çalışma ve daha az uyarlanabilir (eminim orada bir istisna vardır)

Eğer verdiğiniz durumda ben sadece bağımlılık enjeksiyon kurulum çok daha kolay olacağını düşünüyorum bu yüzden bu sahte bir test olarak sayma ilgilenmiyorum kod alay edebilirsiniz. Sadece "Merhaba ben depo aramaya söylemiştim biliyorum ama burada yerine veri var 'gereken' dönüş size yöntemini anlatmak göz kırpıyor " Şimdi bu veriler sadece o bölümü test ediyoruz biliyorum hiç değişmez, çünkü kullanımları bu veri değil, verilerin fiilen alınması.

Temel olarak istediğiniz testi yaparken Entegrasyon (işlevsellik) Bir işlev parçasını baştan sona test eden testler ve her kod parçasını (genellikle yöntem veya işlev düzeyinde) bağımsız olarak test eden tam birim testi.

Fikir, tüm işlevselliğin çalıştığından emin olmak istiyorsanız, eğer çalışmıyorsa kodun tam parçasını bilmek istiyorsanız.

Bu CAN Dependency Injection olmadan yapılabilir, ancak genellikle proje olarak o yerde Dependency Injection olmadan bunu yapmak için daha hantal hale büyür edilebilir. (HER ZAMAN projenizin büyüyeceğini varsayalım! Hızlı bir şekilde yükselen bir proje bulmaktan ziyade yararlı becerilere ihtiyaç duymadan pratik yapmak daha iyidir ve işler başladıktan sonra ciddi yeniden düzenleme ve yeniden yapılandırma gerektirir.)


1
Tüm bağımlılık grafiğini değil büyük bir kısmını kullanarak test yazmak aslında DI için iyi bir argüman.
oberlies

1
DI ile dikkat çekmeye değer, kodunuzun tüm parçalarını programlı olarak kolayca değiştirebilirsiniz. Bir sistemin uzak hizmetler tarafından kesintilere ve performans sorunlarına tepki vermek için tamamen farklı bir sınıfla bir arabirimi temizleyip yeniden enjekte ettiği durumlar gördüm. Bununla başa çıkmanın daha iyi bir yolu olabilirdi, ama şaşırtıcı derecede iyi çalıştı.
RualStorge

0

Bahsetmek gibi başka yanıtında , burada sorun, sınıf istememizdir Abağımlı bazı sınıfınınB sabit kodlama olmadan hangi sınıf Btek yolu sınıfını içe çünkü A. This kaynak koduna kullanılan Java ve C # imkansızdır buna küresel olarak benzersiz bir adla atıfta bulunmaktır.

Bir arabirim kullanarak sabit kodlu sınıf bağımlılığını geçici olarak çözebilirsiniz, ancak yine de ellerinizi arabirimin bir örneğine almanız gerekir ve yapıcıları arayamazsınız veya kare 1'e geri dönersiniz. aksi takdirde bağımlılıklarını yaratabilecek olan bu sorumluluğu başkasına iter. Ve bağımlılıkları da aynı şeyi yapıyor. Böylece , bir sınıfın her örneğine her ihtiyacınız olduğunda, tüm bağımlılık ağacını manuel olarak oluşturursunuz, oysa A sınıfının B'ye doğrudan bağlı olduğu durumda new A(), o kurucu çağrısını arayabilir new B()ve bu şekilde devam edebilirsiniz.

Bir bağımlılık enjeksiyon çerçevesi, sınıflar arasındaki eşlemeleri belirtmenize ve sizin için bağımlılık ağacını oluşturmanıza izin vererek bunu çözmeye çalışır. Burada dikkat edilmesi gereken nokta, eşlemeleri sıkıştırdığınızda, birinci sınıf bir kavram olarak haritalama modüllerini destekleyen dillerde olduğu gibi derleme zamanında değil, çalışma zamanında öğreneceksiniz.


0

Bence bu büyük bir yanlış anlama.

Guice bir bağımlılık enjeksiyon çerçevesidir . DI'yi otomatik yapar . Alıntıladığınız alıntıda yaptıkları nokta, Guice'in örneğinizde sunduğunuz "test edilebilir kurucuyu" manuel olarak oluşturma ihtiyacını ortadan kaldırabilmesidir. Bağımlılık enjeksiyonunun kendisi ile hiçbir ilgisi yoktur.

Bu kurucu:

BillingService(CreditCardProcessor processor, TransactionLog transactionLog)
{
    this.processor = processor;
    this.transactionLog = transactionLog;
}

zaten bağımlılık enjeksiyonu kullanıyor. Temelde sadece DI kullanımının kolay olduğunu söylediniz.

Guice'in çözdüğü sorun, bu kurucuyu kullanmak için, artık bir yerde bir nesne grafiği kurucu koduna sahip olmanız ve zaten oluşturulmuş nesneleri bu kurucunun argümanları olarak manuel olarak geçirmenizdir. Guice, hangi gerçek uygulama sınıflarının bunlara CreditCardProcessorve TransactionLogarayüzlere karşılık geleceğini yapılandırabileceğiniz tek bir yere sahip olmanızı sağlar. Bu yapılandırmadan sonra, BillingServiceGuice kullanarak her oluşturduğunuzda , bu sınıflar otomatik olarak yapıcıya aktarılır.

Bağımlılık enjeksiyon çerçevesinin yaptığı budur. Ancak sunduğunuz kurucunun kendisi zaten bağımlılık enjeksiyon prensibinin bir uygulamasıdır . IoC kapları ve DI çerçeveleri, ilgili ilkeleri otomatikleştirmek için kullanılan araçlardır, ancak her şeyi elle yapmanıza engel olan hiçbir şey yoktur, bütün mesele buydu.

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.