Bağımlılık enjeksiyonu; Kazan kodunu azaltmak için iyi uygulamalar


25

Basit bir sorum var ve bir cevabı olduğundan bile emin değilim ama deneyelim. C ++ 'da kodlama yapıyorum ve küresel durumdan kaçınmak için bağımlılık enjeksiyonunu kullanıyorum. Bu oldukça iyi çalışıyor ve beklenmedik / tanımsız davranışlarda çok sık karşılaşmıyorum.

Ancak, projem büyüdükçe, kazan olarak düşündüğüm birçok kod yazdığımı fark ediyorum. Daha da kötüsü: gerçek koddan ziyade daha fazla kodlu kod olması, bazen anlaşılmasını zorlaştırır.

Hiçbir şey iyi bir örnek olamaz, o yüzden gidelim:

Time nesneleri oluşturan TimeFactory adında bir sınıfım var.

Daha fazla ayrıntı için (alakalı olduğundan emin değilsiniz): Zaman nesneleri oldukça karmaşıktır, çünkü Zaman farklı biçimlerde olabilir ve aralarındaki dönüşüm ne doğrusal ne de anlaşılır değildir. Her "Zaman", dönüşümleri işlemek için bir Senkronize Edici içerir ve aynı, düzgün bir şekilde başlatılmış, senkronize edici olduğundan emin olmak için bir TimeFactory kullanıyorum. TimeFactory'nin yalnızca bir örneği vardır ve uygulama genelidir, bu nedenle singleton için uygun olur, ancak değişken olduğundan, bunu singleton yapmak istemiyorum

Uygulamamda, bir çok sınıf Time nesneleri oluşturmalı. Bazen bu sınıflar derinden iç içe geçmiştir.

Diyelim ki B sınıfı örnekleri içeren bir A sınıfı var ve D sınıfına kadar. D Sınıfının Time nesneleri yaratması gerekiyor.

Naif uygulamamda TimeFactory'yi A sınıfı kurucusuna, bu alanı B sınıfı kurucusuna, vb.

Şimdi, TimeFactory gibi birkaç sınıf ve yukarıdaki gibi birkaç sınıf hiyerarşisine sahip olduğumu hayal edin: Dependancy enjeksiyonunu kullanacağım tüm esnekliği ve okunabilirliği kaybediyorum.

Uygulamamda büyük bir tasarım hatası olup olmadığını merak etmeye başlıyorum ... Ya da bu, bağımlılık enjeksiyonunu kullanmak için gerekli bir kötülük mü?

Ne düşünüyorsun ?


5
Bu soruyu yığma akışı yerine programcılara gönderdim, çünkü anladığım gibi, programcılar tasarıma ilişkin sorgular için daha fazla ve yığılma akışı "derleyicinizin önünde olduğunuzda" sorguları içindir. Yanılıyorsam özür dilerim.
Dinaiz

2
boy yönelimli programlama, bu görevler için de iyidir - en.wikipedia.org/wiki/Aspect-oriented_programming
EL Yusubov

A, B, C ve D sınıfları için A sınıfı fabrika mı? Eğer değilse neden onlardan örnekler yaratıyor? Yalnızca D sınıfı Time-object'a ihtiyaç duyuyorsa, neden TimeFactory ile başka bir sınıfa enjekte ettiniz? D sınıfı gerçekten TimeFactory'ye ihtiyaç duyuyor mu, yoksa sadece bir Time örneği enjekte edilebilir mi?
simoraman

D'nin Time nesneleri yaratması gerekir, sadece D'nin sahip olması gereken bilgiyle. Yorumunuzun geri kalanını düşünmek zorundayım.
Kodumda

“kim kim yaratıyor” IoC konteynerleri tarafından çözüldü, detaylara verilen cevabımı görün.
GameDeveloper,

Yanıtlar:


23

Uygulamamda, çok sayıda sınıf Time nesneleri oluşturmalı

TimeSınıfınızın, uygulamanızın "genel altyapısına" ait olması gereken çok basit bir veri türü gibi görünüyor . DI bu gibi sınıflar için iyi çalışmıyor. Gibi bir sınıfın string, dizeleri kullanan kodun her bölümüne enjekte edilmesi gerekiyorsa ve bir stringFactorydizeyi yeni dizeler oluşturmanın tek yolu olarak kullanmanız gerekecekse, bunun ne anlama geldiğini düşünün - programınızın okunabilirliği bir sırasına göre azalacaktır. büyüklüğü.

Bu yüzden benim önerim: gibi genel veri türleri için DI kullanmayın Time. TimeSınıfın kendisi için birim sınamaları yazın ve bittiğinde, programınızdaki her yerde, stringsınıf veya vectorsınıf veya standart lib'in herhangi bir sınıfında kullanın. Gerçekten birbirinden ayrılması gereken parçalar için DI kullanın.


Tamamen doğru yaptın. "Programınızın okunabilirliği bir büyüklük sırasına göre azalacaktır." : aynen öyle oldu evet. Hala bir fabrika kullanmak istersem, o zaman bunu bir singleton yapmak zor değil mi?
Dinaiz

3
@Dinaiz: Fikir, Timeprogramınızın diğer bölümleri arasında sıkı bağlantıyı kabul etmektir. Böylece, sıkı bağlantıyı da kabul edebilirsin TimeFactory. Bununla birlikte, önleyeceğim şey, programınız için kötü yan etkilere neden olabilen ve genel testler yapıp yeniden kullanmayı çok zorlaştıran devlete sahip (örneğin yerel bir bilgi veya benzeri bir şey) olan tek bir küresel TimeFactorynesneye sahip olmaktır. Ya vatansız hale getirin ya da singleton olarak kullanmayın.
Doktor Brown,

Aslında bir devlete sahip olması gerekir, bu yüzden "Bunu bir singleton olarak kullanma" derken, "dependancy enjeksiyonunu kullanmaya devam et, yani örneği ihtiyaç duyan her nesneye geçir" demek istiyorsun, değil mi?
Dinaiz

1
@Dinaiz: dürüst olmak gerekirse, bu durum devletin türüne, programınızın tür ve sayı bölümlerini Timeve TimeFactorybuna bağlı olarak gelecekteki uzantılar için ihtiyaç duyduğunuz "evrimlilik" derecesine bağlıdır TimeFactory.
Doktor Brown,

Tamam, dependancy enjekte etmeye çalışacağım ama o zaman çok fazla kazan gerektirmeyen daha akıllı bir tasarıma sahip olacağım! çok teşekkürler doktor '
Dinaiz

4

"Bağımlılık enjeksiyonunu kullanacağımı sandığım tüm esnekliği ve okunabilirliği kaybediyorum" derken ne demek istiyorsun? - DI okunabilirlikle ilgili değil. Nesneler arasındaki bağımlılığı ayırmakla ilgili.

A Sınıfı B Sınıfı yaratan, B Sınıfı C Sınıfı yaratan ve C Sınıfı D Sınıfı yaratan sesin var gibi.

Sahip olmanız gereken, B Sınıfına A Sınıfına enjekte edilmiştir. Sınıf B'ye Sınıf B'ye enjekte edilmiştir. Sınıf D'ye Sınıf C'ye enjekte edilmiştir.


DI okunabilirlik hakkında olabilir. Bir yöntemin ortasında beliren "sihirli" bir global değişkeniniz varsa, kodu (bakım açısından) anlamanıza yardımcı olmaz. Bu değişken nereden geliyor? Onun sahibi kim? Bunu kim başlatır? Ve bunun gibi ... İkinci konu için, muhtemelen haklısın ama benim durumumda geçerli olduğunu sanmıyorum.
Cevaplamak

DI okunabilirliği arttırır, çünkü "yeni / sil" sihirli bir şekilde kodunuzdan çıkarılır. Dinamik tahsisat kodu hakkında endişelenmenize gerek yoksa, okunması daha kolaydır.
GameDeveloper,

Ayrıca, kodun biraz daha güçlendiğini söylemeyi de unutmayın, çünkü artık "bir bağımlılığın nasıl yaratıldığı", "sadece ona ihtiyaç" hakkında varsayımlarda bulunamaz. Ve bu sınıfın bakımını kolaylaştırıyor .. cevabımı gör
GameDeveloper 21.03.2013

3

Neden zaman fabrikasını bir singleton yapmak istemediğinden emin değilim. Uygulamanızın sadece bir örneği varsa, bir singleton fiilidir.

Bununla birlikte, değişken bir nesneyi, blokların senkronize edilmesiyle doğru bir şekilde korunmadığı durumlar dışında, bu durumda, tekil kalmamasının bir nedeni olmadığı çok tehlikelidir.

Bağımlılık enjeksiyonu yapmak istiyorsanız, bir ek açıklama kullanarak parametreleri otomatik olarak atamanıza izin verecek yay veya diğer bağımlılık enjeksiyon çerçevelerine bakmak isteyebilirsiniz.


Bahar java için ve ben C ++ kullanıyorum. Peki neden 2 kişi seni düşürdü?
Yazınızda

Düşüşün, belki de ilkbaharın yanı sıra soruyu kendi başınıza yanıtlamamayacağını varsayıyoruz. Bununla birlikte, bir Singleton kullanmanın önerisi aklımda geçerli biriydi ve soruyu cevaplamayabilir - ama geçerli bir alternatif önerir ve ilginç bir tasarım sorunu ortaya çıkarır. Bu nedenle + 1 yaptım.
Fergus In London,

1

Bu, Doc Brown'a tamamlayıcı bir cevap olmayı ve Dinaiz'in hala Soru ile ilgili cevapsız yorumlarına cevap vermeyi amaçlar.

Muhtemelen ihtiyacınız olan şey DI yapmak için bir çerçevedir. Karmaşık hiyerarşilere sahip olmak, mutlaka kötü tasarım anlamına gelmez, ancak doğrudan D'ye enjekte etmek yerine TimeFactory aşağıdan yukarıya (A'dan D'ye) enjekte etmek zorunda kalırsanız, muhtemelen Bağımlılık Enjeksiyonu yapma biçiminizde yanlış bir şey vardır.

Bir singleton? Hayır teşekkürler. Yalnızca bir istance'a ihtiyacınız varsa bunu uygulama bağlamınızda paylaşmasını sağlayın (Infector ++ gibi bir DIC için IoC kabı kullanmak, sadece TimeFactory'yi tek istance olarak bağlamak için gereklidir), işte örnek (bu arada C ++ 11, fakat C ++. Zaten C ++ 11 için mi? Ücretsiz Sızdırmaz uygulama olsun):

Infector::Container ioc; //your app's context

ioc.bindSingleAsNothing<TimeFactory>(); //declare TimeFactory to be shared
ioc.wire<TimeFactory>(); //wire its constructor 

// if you want to be sure TimeFactory is created at startup just request it
// (else it will be created lazily only when needed)
auto myTimeFactory = ioc.buildSingle<TimeFactory>();

Şimdi bir IoC kabının en iyi noktası, "D" sınıfı zaman fabrikasına ihtiyaç duyuyorsa, zaman fabrikasını D sınıfı için kurucu parametre olarak koymanız durumunda, zaman fabrikasını D'ye kadar geçirmenize gerek kalmamasıdır.

ioc.bindAsNothing<A>(); //declare class A
ioc.bindAsNothing<B>(); //declare class B
ioc.bindAsNothing<D>(); //declare class D

//constructors setup
ioc.wire<D, TimeFactory>(); //time factory injected to class D
ioc.wire<B, D>(); //class D injected to class B
ioc.wire<A, B>(); //class B injected to class A

Gördüğünüz gibi TimeFactory'yi sadece bir kez enjekte ediyorsunuz. "A" nasıl kullanılır? Çok basit, her sınıfa enjekte edilir, ana yapıya yerleştirilir veya bir fabrika ile onaylanır.

auto myA1 = ioc.build<A>(); //A is not "single" so many different istances
auto myA2 = ioc.build<A>(); //can live at same time

A sınıfı her yaratışınızda, D ve D'ye kadar olan tüm bağımlılıklara otomatik olarak (tembel tanımlama) enjekte edilir, TimeFactory ile enjekte edilir, böylece sadece 1 yöntemi çağırdığınızda tam hiyerarşinize hazır olursunuz (ve hatta karmaşık hiyerarşiler bu şekilde çözülür) Çok sayıda kazan plakası kodunun çıkarılması): "new / delete" komutunu çağırmanız gerekmez ve bu çok önemlidir çünkü uygulama mantığını yapıştırıcı kodundan ayırabilirsiniz.

D, yalnızca D’nin sahip olabileceği bilgileri içeren Time nesneleri yaratabilir

Bu çok kolay, TimeFactory'nizde "create" yönteminiz var, sonra sadece farklı bir "create (params)" imzası kullanın ve bitirdiniz. Bağımlılık olmayan parametreler genellikle bu şekilde çözülür. Bu aynı zamanda, sadece fazladan kazan plakası eklediğinden, "dizeler" veya "tam sayılar" gibi şeyleri enjekte etme görevini de ortadan kaldırır.

Kim kimi yaratır? IoC konteyneri, durumlar ve fabrikalar yaratır, fabrikalar geri kalanı yaratır (fabrikalar isteğe bağlı parametrelerle farklı nesneler oluşturabilir, bu nedenle fabrikalar için bir duruma ihtiyacınız yoktur). Fabrikaları hala IoC Konteyner için sarmalayıcı olarak kullanabilirsiniz: genel olarak konuşursak, IoC Konteynerinin enjekte edilmesi çok kötüdür ve bir servis bulucu kullanmakla aynıdır. Bazı insanlar IoC Konteynerini bir fabrikaya sararak sorunu çözdü (bu kesinlikle gerekli değildir, ancak hiyerarşinin Konteyner tarafından çözülmesi ve tüm fabrikalarınızın bakımı daha kolay hale gelme avantajına sahiptir).

//factory method
std::unique_ptr<myType> create(params){
    auto istance = ioc->build<myType>(); //this code's agnostic to "myType" hierarchy
    istance->setParams(params); //the customization you needed
    return std::move(istance); 
}

Ayrıca bağımlılık enjeksiyonunu kötüye kullanmayınız, basit tipler sadece sınıf üyeleri veya yerel kapsamlı değişkenler olabilir. Bu çok açık gözüküyor ancak insanların izin verdikleri bir DI çerçevesi olduğu için "std :: vector" enjekte ettiklerini gördüm. Demeter'in yasasını her zaman hatırlayın: "Yalnızca gerçekten enjekte etmeniz gerekenleri enjekte edin"


Vay, cevabını daha önce görmedim, üzgünüm! Çok bilgilendirici efendim! Olumlu oy
Dinaiz

0

A, B ve C sınıflarınızın da Time örnekleri mi oluşturması gerekiyor, yoksa yalnızca D sınıfı mı? Yalnızca D sınıfıysa, A ve B TimeFactory hakkında hiçbir şey bilmemelidir. C sınıfı içinde bir TimeFactory örneği oluşturun ve onu D sınıfına iletin. "Örnek oluşturun" derken, C sınıfının TimeFactory'yi başlatmaktan sorumlu olmak zorunda olduğu anlamına gelmediğini unutmayın. DClassFactory'yi B sınıfından alabilir ve DClassFactory Time örneğinin nasıl oluşturulacağını bilir.

Herhangi bir DI çerçevesine sahip olmadığımda sıklıkla kullandığım bir teknik, bir fabrikayı kabul eden ve diğeri varsayılan bir fabrika oluşturan iki yapıcı sağlamaktır. İkincisi genellikle korumalı / paket erişimine sahiptir ve temel olarak birim testleri için kullanılır.


-1

Yakın zamanda destek için önerilen başka bir C ++ bağımlılık enjeksiyon çerçevesi daha uyguladım - https://github.com/krzysztof-jusiak/di - kütüphane makrodan daha az (ücretsiz), sadece başlık, C ++ 03 / C ++ 11 / C ++ 14 kütüphanesi tür güvenli, derleme zamanı, makro içermeyen yapıcı bağımlılık enjeksiyonu sağlar.


3
P.SE'deki Eski soruları cevaplamak projenizi tanıtmanın kötü bir yoludur. Orada bir soru ile ilgisi olabilecek on binlerce proje olduğunu düşünün. Tüm yazarları burada böyle yayınlanırsa, sitenin cevap kalitesi düşecektir.
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.