.NET'te DI uygulamanın "doğru" yolu nedir?


22

Bağımlılık enjeksiyonunu nispeten büyük bir uygulamada uygulamak istiyorum ancak bu konuda deneyimim yok. Birlik ve Ninject gibi mevcut kavramı ve birkaç IoC uygulaması ve bağımlılık enjektörleri çalıştım. Ancak, beni rahatsız eden bir şey var. Uygulamamda örnek oluşturmayı nasıl organize etmeliyim?

Düşündüğüm şey, birkaç belirli sınıf tipi için nesne oluşturma mantığı içerecek birkaç özel fabrika oluşturabileceğim. Temel olarak, bu sınıftaki statik bir çekirdek örneğinin Ninject Get () yöntemini çağıran bir yöntemi olan statik bir sınıf.

Başvurumda bağımlılık enjeksiyonunu uygulamak doğru bir yaklaşım mı olacak yoksa başka bir ilkeye göre mi uygulamalıyım?


5
Orada olduğunu düşünmüyorum Doğru yol, ancak bir çok usul, projeniz bağlı. Diğerlerine bağlı kalırım ve yapıcı enjeksiyonunu öneririm, çünkü her bağımlılığın tek bir noktaya enjekte edildiğinden emin olabilirsiniz. Ayrıca, yapıcı imzaları çok uzarsa, sınıfların çok fazla şey yaptığını bileceksiniz.
Paul Kertscher

Ne tür bir .net projesi oluşturduğunuzu bilmeden cevap vermek zor. Örneğin, WPF için iyi bir cevap MVC için kötü bir cevap olabilir.
JMK,

Tüm bağımlılık kayıtlarını çözüm için veya her proje için bir DI modülünde ve muhtemelen derinlemesine test etmek istediğiniz bazı testler için bir tanesini düzenlemek güzel. Oh evet, elbette yapıcı enjeksiyonunu kullanmalısınız, diğer şeyler daha gelişmiş / çılgın kullanımlar içindir.
Mark Rogers,

Yanıtlar:


30

Kullanacağınız araç hakkında henüz düşünmeyin. DIC'yi bir IoC Container olmadan yapabilirsiniz.

İlk nokta: Mark Seemann’ın .Net’te DI hakkında çok iyi bir kitabı var.

İkincisi: kompozisyon kökü. Tüm kurulumun projenin giriş noktasında yapıldığından emin olun. Kodunuzun geri kalan kısmı, kullanılan herhangi bir araç hakkında değil, enjeksiyonlardan haberi olmalı.

Üçüncüsü: Yapıcı Enjeksiyonu, gitmenin en muhtemel yoludur (istemeyeceğiniz durumlar vardır, ancak pek fazla değil).

Dördüncüsü: Enjeksiyon amacıyla gereksiz arayüzler / sınıflar oluşturmamak için lamda fabrikalarını ve benzeri özellikleri kullanmayı düşünün.


5
Tüm mükemmel tavsiyeler; özellikle ilk kısım: saf DI'nin nasıl yapıldığını öğrenin ve ardından bu yaklaşım için gerekli olan kazan kodunun miktarını azaltabilecek IoC konteynerlerine bakmaya başlayın.
David Arno,

6
... veya IoC konteynerlerini hep birlikte atlayın - statik doğrulamanın tüm faydalarını korumak için.
Den

Bu tavsiyeyi DI'ye girmeden önce iyi bir başlangıç ​​noktası olarak seviyorum ve aslında Mark Seemann kitabım var.
Snoop

Bu cevabı ikinci verebilirim. Oldukça büyük bir uygulamada fakir-mans-DI (el yazısı önyükleyici) uygulamasını, önyükleyici mantığının parçalarını uygulamayı oluşturan modüllere ayırarak başarıyla kullandık.
peruk

1
Dikkatli ol. Lamda enjeksiyonlarını kullanmak, özellikle test kaynaklı tasarım hasarı türlerinde deliliğe giden hızlı bir yol olabilir. Biliyorum. O yoldan gittim.
jpmc26

13

Sorunuz için iki bölüm vardır - DI'nin doğru şekilde nasıl uygulanacağı ve DI'yi kullanmak için büyük bir uygulamanın nasıl yeniden yapılandırılacağı.

İlk bölüm @Miyamoto Akira (özellikle Mark Seemann’ın .net’e bağımlılık enjeksiyonu ”kitabını okumanızı tavsiye eder) tarafından iyi bir şekilde cevaplandı. Marks blog ayrıca iyi bir ücretsiz kaynak.

İkinci kısım daha karmaşık bir anlaşma.

İyi bir ilk adım, tüm kurgulamayı sınıf kurucularına taşımaktır - bağımlılıkları enjekte etmemek, sadece newkurucu olarak çağırdığınızdan emin olmak .

Bu, yaptığınız tüm SRP ihlallerini vurgulayacaktır, böylece sınıfı daha küçük ortak çalışanlara bölmeye başlayabilirsiniz.

Bulatacağınız bir sonraki sayı, inşaat için çalışma zamanı parametrelerine dayanan sınıflar olacaktır. Bunu genellikle basit fabrikalar oluşturarak, çoğu zaman birlikte Func<param,type>, kurucuda başlatıp yöntemlerde çağırarak düzeltebilirsiniz .

Bir sonraki adım, bağımlılığınız için arayüzler oluşturmak ve bu arayüzler dışındaki sınıflarınıza ikinci bir kurucu eklemek olacaktır. Parametresiz kurucunuz somut örnekleri yeniler ve bunları yeni kurucuya iletir. Bu genellikle 'B * stard Injection' veya 'Poor mans DI' olarak adlandırılır.

Bu size bazı ünite testleri yapma yeteneği verecektir ve eğer refactorun asıl amacı buysa, durduğunuz yerde olabilir. Yeni kod kurucu enjeksiyonla yazılacak, ancak eski kodunuz yazıldığı gibi çalışmaya devam edebilir, ancak test edilebilir.

Elbette daha ileri gidebilirsiniz. Bir IOC kabı kullanmayı planlıyorsanız, bir sonraki adım new, parametresiz kurucularınızdaki tüm doğrudan çağrıları , IOC kabına statik çağrılarla değiştirmek, esasen (ab) bir servis bulucu olarak kullanmak olabilir.

Bu daha önce olduğu gibi başa çıkmak için daha fazla çalışma zamanı yapıcı parametresi örneği fırlatır.

Bu yapıldıktan sonra, parametresiz yapıcıları ve refactor'ü saf DI'ye kaldırmaya başlayabilirsiniz.

Nihayetinde bu çok fazla iş olacak, bu yüzden neden yapmak istediğinize karar verdiğinizden emin olun ve kod tabanının refactor'dan en fazla yarar sağlayacak olan kısımlarına öncelik verin


3
Çok ayrıntılı bir cevap için teşekkür ederiz. Bana, karşılaştığım konuya nasıl yaklaşılacağı hakkında birkaç fikir verdiniz. Uygulamanın tüm mimarisi zaten akılda IoC ile inşa edilmiştir. DI kullanmak istememin asıl nedeni, birim test bile değil, bir bonus olarak geliyor, ancak uygulamamın özünde tanımlanmış farklı arayüzler için farklı uygulamaları mümkün olan en az çabayla değiştirebilme yeteneği. Söz konusu uygulama sürekli değişen bir ortamda çalışıyor ve sıklıkla ortamdaki değişikliklere göre yeni uygulamalar kullanmak için uygulamanın bölümlerini değiştirmem gerekiyor.
user3223738

1
Yardımcı olabileceğime sevindim ve evet, DI'nin en büyük avantajının gevşek kaplin ve bunun bir araya getirerek kolayca yapılandırabildiğine, birim testinin iyi bir yan etki olduğuna katılıyorum.
Steve

1

Öncelikle, yeni bir projeye başlamak yerine mevcut bir projeyi yeniden canlandırarak bunu kendiniz için daha da zorlaştırdığınızı belirtmek istiyorum.

Büyük bir uygulama olduğunu söylediniz, bu yüzden başlamak için küçük bir bileşen seçin. Tercihen, başka hiçbir şey tarafından kullanılmayan bir 'yaprak düğümü' bileşeni. Bu uygulamadaki otomatik testlerin durumunun ne olduğunu bilmiyorum, ancak bu bileşen için tüm birim testlerini yıkıyor olacaksınız. Öyleyse buna hazır olun. Adım 0, henüz mevcut değilse, değiştireceğiniz bileşen için entegrasyon testleri yazıyor. Son çare olarak (test altyapısı yok; yazmak için giriş yok), bu bileşenin çalıştığını doğrulamak için yapabileceğiniz bir dizi manuel test yapın.

DI refactor için hedefinizi belirtmenin en basit yolu, 'new' operatörünün tüm örneklerini bu bileşenden kaldırmak istediğinizdir. Bunlar genellikle iki kategoriye ayrılır:

  1. Değişmeyen üye değişkeni: Bunlar bir kez ayarlanmış (tipik olarak kurucuda) ve nesnenin kullanım ömrü boyunca atanmamış değişkenlerdir. Bunlar için nesnenin bir örneğini kurucuya enjekte edebilirsiniz. Bu nesneleri elden çıkarmaktan genellikle siz sorumlu değilsiniz (burada asla söylemek istemem ama gerçekten bu sorumluluğa sahip olmamanız gerekir).

  2. Varyant üye değişkeni / yöntem değişkeni: Bunlar nesnenin kullanım ömrü boyunca bir noktada çöp toplanacak değişkenlerdir. Bunlar için, bu örnekleri sağlamak için sınıfınıza bir fabrika enjekte etmek isteyeceksiniz. Bir fabrika tarafından oluşturulan eşyaları elden çıkarmak sizin sorumluluğunuzdadır.

IoC kabınız (kulağa benziyor), bu nesnelerin somutlaştırılması ve fabrika arayüzlerinizi hayata geçirme sorumluluğunu üstlenir. Değiştirdiğiniz bileşeni ne kullanıyorsanız kullanın, IoC kabı hakkında bilmeniz gerekir, böylece bileşeninizi alabilir.

Yukarıdakileri tamamladığınızda, seçtiğiniz bileşendeki DI'den almayı umduğunuz faydaları elde edebileceksiniz. Şimdi bu birim testlerini eklemek / düzeltmek için iyi bir zaman olurdu. Mevcut birim testleri varsa, gerçek nesneleri enjekte ederek bunları yama yapmak ya da alay kullanarak yeni birim testleri yazmak isteyip istemediğinize karar vermeniz gerekir.

'Basitçe' uygulamanızın her bir bileşeni için yukarıdakileri tekrarlayın, referansı yalnızca ana bilmesi gerekene kadar, IoC kabına kadar taşıyın.


1
İyi tavsiye: 'BÜYÜK yeniden yazma' yerine küçük bir bileşenle başlayın
Daniel Hollinrake

0

Doğru yaklaşım, eğer kullanıyorsanız, yapıcı enjeksiyonunu kullanmaktır.

Düşündüğüm şey, birkaç belirli sınıf tipi için nesne oluşturma mantığı içerecek birkaç özel fabrika oluşturabileceğim. Temel olarak, bu sınıftaki statik bir çekirdek örneğinin Ninject Get () yöntemini çağıran bir yöntemi olan statik bir sınıf.

sonra bağımlılık enjeksiyonundan ziyade servis bulucu ile sonuçlanırsınız.


Emin. Yapıcı enjeksiyon Diyelim ki bir arayüz uygulamasını argümanlardan biri olarak kabul eden bir sınıfım var. Ancak yine de arayüz uygulamasının bir örneğini yaratmam ve onu bu kurucuya bir yere aktarmam gerekiyor. Tercihen, bazı merkezi kod parçası olmalıdır.
user3223738

2
Hayır, DI kabını başlattığınızda, arabirimlerin uygulanmasını belirtmeniz gerekir ve DI kabı örneği oluşturacak ve yapıcıya enjekte edecektir.
Low Flying Pelican

Şahsen, aşırı dozda kurucu enjeksiyon buluyorum. 10 farklı servisin enjekte edildiğini ve bir işlev çağrısı için gerçekte sadece birinin gerekli olduğunu çok sık gördüm.
urbanhusky

2
10 farklı servis enjekte edilirse, bunun nedeni birinin daha küçük bileşenlere bölünmesi gereken SRP'yi ihlal etmesidir.
Low Flying Pelican

1
@Fabio Soru, sizi ne alacağıdır. Bir düzine tamamen farklı şeylerle uğraşan dev bir sınıfa sahip olmanın iyi bir tasarım olduğu bir örnek görmedim. DI'nin yaptığı tek şey tüm bu ihlalleri daha açık hale getirmek.
Voo

0

Kullanmak istediğini ama nedenini söylemediğini söylüyorsun.

DI, arayüzlerden salgı üretmek için bir mekanizma sağlamaktan başka bir şey değildir.

Tek başına bu gelir DIP . Eğer kodunuz zaten bu tarzda yazılmışsa ve atıfların üretildiği tek bir yer varsa, DI partiye daha fazla bir şey getirmez. DI çerçeve kodunu buraya eklemek basitçe şişirir ve kod tabanınızı gizler.

Eğer varsayarsak do açıkça görünür olması için kullanmak istiyorum, genellikle erken uygulamada fabrika / oluşturucu / kabı (ya da herneyse) kurdu.

NB, Ninject / StructureMap ya da her neyse, kendi isteğinizi kendiniz yapmak çok kolaydır. Bununla birlikte, makul bir personel devrine sahipseniz, jantları tanınmış bir çerçeve kullanması için ya da en azından o tarzda bir yazma eğrisi kullanmayacak şekilde yazdırabilir.


0

Aslında, “doğru” yol, kesinlikle başka bir seçenek olmadığı sürece (fabrika testlerinde ve bazı cihazlarda olduğu gibi - üretim kodu için bir fabrika kullanmazsınız) kesinlikle bir fabrika kullanmamaktır! Bunu yapmak aslında bir anti-kalıptır ve ne pahasına olursa olsun kaçınılmalıdır. Bir DI konteyner arkasında bütün mesele gadget işi yapmak için izin vermektir için size.

Önceki bir yayında yukarıda belirtildiği gibi, IoC gadget'ınızın uygulamanızda çeşitli bağımlı nesnelerin yaratılması sorumluluğunu üstlenmesini istiyorsunuz. Bu, DI gadget'ınızın çeşitli örnekleri kendisi oluşturmasına ve yönetmesine izin vermek anlamına gelir. Bu DI'nin arkasındaki asıl nokta - nesnelerinizin bağlı oldukları nesneleri nasıl yaratacağını ve / veya yöneteceğini ASLA bilmemesi gerekir. Aksi taktirde gevşek bağlantı kopar .

Mevcut bir uygulamayı tüm DI'ye dönüştürmek büyük bir adımdır, ancak bunu yaparken bariz zorlukları bir kenara koymak, aynı zamanda (sadece hayatınızı biraz kolaylaştırmak için) ciltleme işlemlerinizi otomatik olarak gerçekleştirecek bir DI aracını keşfetmek isteyecektir. (Ninject gibi bir şeyin özü "kernel.Bind<someInterface>().To<someConcreteClass>()", arayüz bildirimlerinizi bu arabirimleri uygulamak için kullanmak istediğiniz somut sınıflarla eşleştirmek için yaptığınız çağrılardır. DI gadget'ınızın yapıcı çağrılarınızı engellemesine ve sağlamasına olanak sağlayan "Bağlama" çağrılarıdır. gerekli bağımlı nesne örnekleri: Bazı sınıflar için tipik bir kurucu (burada gösterilen sözde kod):

public class SomeClass
{
  private ISomeClassA _ClassA;
  private ISomeOtherClassB _ClassB;

  public SomeClass(ISomeClassA aInstanceOfA, ISomeOtherClassB aInstanceOfB)
  {
    if (aInstanceOfA == null)
      throw new NullArgumentException();
    if (aInstanceOfB == null)
      throw new NullArgumentException();
    _ClassA = aInstanceOfA;
    _ClassB = aInstanceOfB;
  }

  public void DoSomething()
  {
    _ClassA.PerformSomeAction();
    _ClassB.PerformSomeOtherActionUsingTheInstanceOfClassA(_ClassA);
  }
}

Not hiçbir yerde bu kodda herhangi bir kod SomeConcreteClassA veya SomeOtherConcreteClassB oluşturulan bu / yönetilen / salınan örneği ya idi. Nitekim, somut sınıflara bile değinilmemiştir. Peki ... büyü nerede oldu?

Uygulamanızın başlangıç ​​bölümünde, aşağıdakiler gerçekleşti (yine, bu sözde kod ama gerçek (Ninject) olayına oldukça yakın ...):

public void StartUp()
{
  kernel.Bind<ISomeClassA>().To<SomeConcreteClassA>();
  kernel.Bind<ISomeOtherClassB>().To<SomeOtherConcreteClassB>();
}

Bu küçük kod parçası, Ninject gadget'ına yapıcıları aramasını, onları taramasını, işlemek için yapılandırılmış olduğu arabirimlerin örneklerini aramasını söyler (bu "Bağlama" çağrılarıdır) ve sonra her yerde somut sınıfın bir örneğini yaratıp değiştirir örnek referanslıdır.

Ninject'i, Ninject.Extensions.Conventions (yine başka bir NuGet paketi) olarak adlandırılan ve bu çalışmanın büyük kısmını sizin için yapacak olan iyi bir araç var. Bunu kendiniz oluştururken yaşayacağınız mükemmel öğrenme deneyiminden uzaklaşmamak, ancak kendinizi başlatmak için bu araştırmanın bir aracı olabilir.

Bellek hizmet ederse, Unity (şu anda Microsoft'tan resmi bir Açık Kaynak projesidir) aynı şeyi yapan bir yöntem çağrısına veya ikisine birden sahipse, diğer araçların benzer yardımcıları vardır.

Hangi yolu seçerseniz seçin, DI eğitiminizin büyük bir kısmı için kesinlikle Mark Seemann'in kitabını okuyun, ancak, yazılım mühendisliği dünyasının (Mark gibi) "Büyük Olanlar" ın bile göz kamaştırıcı hatalar yapabileceğine dikkat edilmelidir - Mark her şeyi unuttu Kitabında Ninject yani sadece Ninject için yazılmış başka bir kaynaktır. Ben ve onun iyi bir okuma var: Bağımlılık Enjeksiyonu için Ninject Mastering


0

"Doğru yol" yoktur, ancak izlenecek birkaç basit ilke vardır:

  • Uygulamanın başlangıcında kompozisyon kökünü oluşturun
  • Kompozisyon kökü oluşturulduktan sonra, referansı DI kabına / çekirdeğine doğru atın (veya en azından uygulamanızdan doğrudan erişilememesi için kapsülleyin)
  • "New" üzerinden örnek oluşturmayın
  • Gerekli tüm bağımlılıkları yapıcıya soyutlama olarak geçirme

Bu kadar. Elbette, bu kanun değil ilkelerdir, ancak onları izlerseniz DI yaptığınızdan emin olabilirsiniz (lütfen hatalıysam beni düzeltin).


Peki, çalışma zamanı sırasında "yeni" ve DI kapsayıcısını bilmeden nesneler nasıl oluşturulur?

NInject durumunda , fabrikaların oluşturulmasını sağlayan bir fabrika uzantısı vardır. Tabii ki, oluşturulan fabrikalar hala çekirdeğe içsel bir yansımaya sahipler, ancak bu uygulamanızdan erişilebilir değil.

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.