Bağımlılık enjeksiyonu alıyorum, ancak birisi IoC kapsayıcısına olan ihtiyacı anlamama yardımcı olabilir mi?


15

Sorunun başka bir tekrarı gibi görünüyorsa özür dilerim, ancak konuyla ilgili her makale bulduğumda çoğunlukla DI'nin ne olduğu hakkında konuşur. Bu yüzden DI alıyorum, ama herkesin girdiği bir IoC konteynerine olan ihtiyacı anlamaya çalışıyorum. Bir IoC konteynerinin amacı sadece bağımlılıkların somut uygulamasını "otomatik olarak çözmek" midir? Belki de sınıflarımın birkaç bağımlılığı yoktur ve belki de bu yüzden büyük bir anlaşma görmüyorum, ancak konteynerin faydasını doğru bir şekilde anladığımdan emin olmak istiyorum.

Genelde iş mantığımı böyle bir şeye benzeyen bir sınıfa ayırırım:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

Yani sadece bir bağımlılığa sahiptir ve bazı durumlarda ikinci veya üçüncü olabilir, ancak çoğu zaman bu olmayabilir. Bunu çağıran her şey kendi repo'sunu geçebilir veya varsayılan repoyu kullanmasına izin verebilir.

Şu anda bir IoC kapsayıcısını anladığım kadarıyla, kabın yaptığı tek şey IDataRepository'yi çözmek. Ama eğer hepsi bu kadarsa, o zaman içinde bir ton değer görmüyorum çünkü operasyonel sınıflarım hiçbir bağımlılık geçmediği zaman zaten bir geri dönüş tanımlıyorlar. Bu aynı yedek repo kullanın, bu repo kayıt / fabrika / konteyner olan bir yerde değiştirebilirsiniz. Ve bu harika, ama öyle mi?


1
Genellikle bağımlılığın varsayılan bir yedek sürümüne sahip olmak gerçekten mantıklı değildir.
Ben Aaronson

Ne demek istiyorsun? "Yedek", birim testler dışında hemen hemen her zaman kullanılan somut sınıftır. Aslında, bu kapta kayıtlı olan aynı sınıf olacaktır.
Sinaestetik

Evet, ancak kapsayıcı ile: (1) kapsayıcıdaki diğer tüm nesneler aynı örneği alır ConcreteRepositoryve (2) ek bağımlılıklar sağlayabilirsiniz ( örneğin, ConcreteRepositorybir veritabanı bağlantısı yaygındır).
Jules

@Sinaestetik Bu her zaman kötü bir fikir demek istemiyorum, ama çoğu zaman uygun değil. Örneğin soğan mimarisini proje referanslarınızla takip etmenizi engelleyecektir. Ayrıca, açık bir varsayılan uygulama olmayabilir. Jules'un dediği gibi, IOC kapsayıcıları sadece bağımlılık türünü seçmekle kalmayıp, aynı zamanda örnekleri paylaşmak ve yaşam döngüsü yönetimi gibi şeyleri de yönetir
Ben Aaronson

"Fonksiyon Parametreleri - ORİJİNAL Bağımlılık Enjeksiyonu!" Yazan tişörtü alacağım.
Graham

Yanıtlar:


2

IoC kapsayıcısı, tek bir bağımlılığınız olduğu durumla ilgili değildir. Bu, 3 bağımlılığınızın olduğu ve bağımlılıkları vb. Olan birkaç bağımlılığın olduğu durumla ilgilidir.

Ayrıca bir bağımlılığın çözünürlüğünü ve bağımlılıkların yaşam döngüsü yönetimini merkezileştirmenize yardımcı olur.


10

IoC kapsayıcısını kullanmak istemenizin birkaç nedeni olabilir.

Referanssız dll

Bir IoC kapsayıcısını başvurulmamış bir dll'den somut bir sınıfı çözmek için kullanabilirsiniz. Bu, bağımlılıkları tamamen soyutlamaya, yani arabirime alabileceğiniz anlamına gelir.

Kullanmaktan kaçının new

Bir IoC kapsayıcısı, newsınıf oluşturmak için anahtar kelimenin kullanımını tamamen kaldırabileceğiniz anlamına gelir . Bunun iki etkisi vardır. Birincisi, sınıflarınızı ayırmasıdır. İkincisi (ilgili) birim testi için alaylara düşebilmenizdir. Bu, özellikle uzun süren bir işlemle etkileşim kurduğunuzda inanılmaz derecede faydalıdır.

Soyutlamalara Karşı Yaz

Somut bağımlılıklarınızı sizin için çözmek için bir IoC konteyneri kullanmak, ihtiyacınız olan her beton sınıfını uygulamak yerine kodunuzu soyutlamalara karşı yazmanıza izin verir. Örneğin, bir veritabanından veri okumak için kodunuza ihtiyacınız olabilir. Veritabanı etkileşim sınıfını yazmak yerine, bunun için bir arayüz yazmanız ve buna karşı kod yazmanız yeterlidir. Diğer kodu test etmeden önce somut veritabanı etkileşim sınıfını geliştirmek yerine geliştirdiğiniz kodun işlevselliğini test etmek için bir sahte kullanabilirsiniz.

Gevrek koddan kaçının

Bir IoC kapsayıcısı kullanmanın bir başka nedeni, bağımlılıklarınızı çözmek için IoC kapsayıcısına güvenerek, bir bağımlılık eklediğinizde veya kaldırdığınızda her bir çağrıyı sınıf yapıcısına değiştirme gereksiniminden kaçınmanızdır. IoC kapsayıcısı bağımlılıklarınızı otomatik olarak çözecektir. Bir kez sınıf oluştururken bu çok büyük bir sorun değil, ancak sınıfı yüzlerce yerde oluştururken çok büyük bir sorun.

Ömür boyu yönetim ve yönetilmeyen kaynak temizleme

Bahseteceğim son sebep, nesne yaşamlarının yönetimidir. IoC kapları genellikle bir nesnenin ömrünü belirleme olanağı sağlar. Nesnenin ömrünü bir IoC kapsayıcısında manuel olarak kodda yönetmeye çalışmak yerine belirtmek çok mantıklıdır. Manuel kullanım ömrü yönetimi çok zor olabilir. Bu, atılması gereken nesnelerle uğraşırken yararlı olabilir. Nesnelerinizin elden çıkarılmasını manuel olarak yönetmek yerine, bazı IoC kapları elden çıkarmayı sizin için yönetir, bu da bellek sızıntılarını önlemeye ve kod tabanınızı basitleştirmeye yardımcı olabilir.

Sağladığınız örnek koddaki sorun, yazdığınız sınıfın ConcreteRepository sınıfına somut bir bağımlılığı olmasıdır. Bir IoC kapsayıcısı bu bağımlılığı ortadan kaldıracaktır.


22
Bunlar IoC kaplarının avantajları değil, fakir adamın DI
Ben Aaronson

IoC kabı olmadan iyi DI kodu yazmak aslında zor olabilir. Evet, avantajlarda bazı çakışmalar var, ancak bunların hepsi bir IoC konteyneri ile en iyi şekilde yararlanan avantajlardır.
Stephen

Yorumumdan bu yana eklediğiniz son iki neden daha kapsayıcıya özgü ve her ikisi de benim düşünceme göre çok güçlü argümanlar
Ben Aaronson

"Yeni kullanmaktan kaçının" - ayrıca statik kod analizini de engeller, bu yüzden şöyle bir şey kullanmaya başlamanız gerekir: hmemcpy.github.io/AgentMulder . Bu paragrafta açıkladığınız diğer faydalar, IoC ile değil DI ile ilgilidir. Ayrıca, yeni kullanmaktan kaçınırsanız, ancak parametreler için arabirimler yerine somut türleri kullanacaksanız , sınıflarınız yine de birleştirilecektir .
Den

1
Genel olarak IoC'yi kusurları olmayan bir şey olarak boyar, örneğin büyük bir dezavantajdan söz edilmez: derleme zamanı yerine bir hata sınıfını çalışma zamanına itme.
Den

2

Tek sorumluluk ilkesine göre her sınıfın sadece tek bir sorumluluğu olmalıdır. Yeni sınıf örnekleri oluşturmak başka bir sorumluluktur, bu nedenle bu tür kodları bir veya daha fazla sınıfta kapsüllemeniz gerekir. Bunu, fabrikalar, inşaatçılar, DI kapları vb.Gibi herhangi bir yaratıcı desen kullanarak yapabilirsiniz.

Kontrolün tersine çevrilmesi ve bağımlılığın tersine çevrilmesi gibi başka ilkeler de vardır. Bu bağlamda, bağımlılıkların somutlaştırılmasıyla ilgilidir. Yüksek seviye sınıfların kullandıkları düşük seviye sınıflardan (bağımlılıklar) ayrılması gerektiğini belirtirler. Arayüzler oluşturarak işleri birbirinden ayırabiliriz. Dolayısıyla, düşük seviyeli sınıfların belirli arayüzleri uygulaması ve yüksek seviyedeki sınıfların bu arayüzleri uygulayan sınıf örneklerini kullanması gerekir. (not: REST düzgün arabirim kısıtlaması, sistem düzeyinde aynı yaklaşımı uygular.)

Örnek olarak bu ilkelerin kombinasyonu (düşük kaliteli kod için özür dilerim, bilmediğim için C # yerine geçici bir dil kullandım):

  1. SRP yok, IoC yok

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. SRP'ye daha yakın, IoC yok

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. Evet SRP, hayır IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. Evet SRP, evet IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

Böylece, her somut uygulamayı aynı arabirimi uygulayan bir başkasıyla değiştirebileceğiniz bir koda sahip olduk. Yani bu iyi çünkü katılan sınıflar birbirinden ayrılıyor, sadece arayüzleri biliyorlar. Gerçekleştirmenin kodunun yeniden kullanılabilir olması diğer bir avantajdır.

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.