Bağımlılık Enjeksiyon yapıcı delilik nasıl önlenir?


300

Yapımlarımın şöyle görünmeye başladığını görüyorum:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

sürekli artan parametre listesi ile. "Konteyner" benim bağımlılık enjeksiyon kabım olduğundan, neden sadece bunu yapamıyorum:

public MyClass(Container con)

her sınıf için? Dezavantajları nelerdir? Bunu yaparsam, yüceltilmiş bir statik kullanıyorum gibi geliyor. Lütfen IoC ve Bağımlılık Enjeksiyonu çılgınlığı hakkındaki düşüncelerinizi paylaşın.


64
konteyneri neden geçiyorsun Sanırım
IOC'yi

33
Yapıcılarınız daha fazla veya daha fazla parametre talep ediyorsa bu sınıflarda çok fazla şey yapıyor olabilirsiniz.
Austin Salonen 10'10

38
Yapıcı enjeksiyonu böyle yapmazsınız. Nesneler IoC konteyneri hakkında hiçbir şey bilmiyorlar, ne de olmalı.
duffymo

Sadece ihtiyacınız olan şeyleri doğrudan DI olarak adlandırdığınız boş bir kurucu oluşturabilirsiniz. Bu, kurucu madnes'i kaldıracak, ancak bir DI Arabirimi kullandığınızdan emin olmanız gerekir. Dürüst olmak gerekirse ... DI, yapıcıya enjekte etmek için yaptığı şey bu olsa da, kimse bunu bu şekilde yapmayacak. doh
Piotr Kula

Yanıtlar:


409

Kapsayıcıyı bir Servis Bulucu olarak kullanırsanız, az çok yüceltilmiş bir statik fabrika olduğu konusunda haklısınız. Birçok nedenden dolayı bunu anti-desen olarak görüyorum .

Konstrüktör Enjeksiyonunun harika faydalarından biri, Tek Sorumluluk Prensibi ihlallerini göze çarpan bir şekilde açık hale getirmesidir .

Bu olduğunda, Cephe Hizmetlerine yeniden bakmanın zamanı geldi . Kısacası, şu anda gereksinim duyduğunuz ince taneli bağımlılıkların bazıları veya tümü arasındaki etkileşimi gizleyen yeni, daha kaba taneli bir arayüz oluşturun.


8
Yeniden düzenleme çabasını tek bir kavram olarak ölçmek için +1; harika :)
Ryan Emerle

46
gerçek mi? bu parametreleri başka bir sınıfa taşımak için sadece bir dolaylı oluşturma oluşturdunuz, ancak bunlar hala orada! onlarla başa çıkmak daha karmaşık.
doldurulamaz

23
@irreputable: Tüm bağımlılıkları Toplu Bir Hizmete taşıdığımız yozlaşmış durumda , fayda sağlamayan başka bir dolaylılık seviyesi olduğunu kabul ediyorum, bu yüzden kelime seçimim biraz kapalı kaldı. Ancak mesele şu ki , ince taneli bağımlılıkların sadece bir kısmını Toplu Hizmete taşıyoruz. Bu, hem yeni Toplam Servis'te hem de geride bırakılan bağımlılıklar için bağımlılık permütasyonlarının sayısını sınırlar. Bu her ikisinin de üstesinden gelmeyi kolaylaştırır.
Mark Seemann

92
En iyi açıklama: "Yapıcı Enjeksiyonunun harika faydalarından biri, Tek Sorumluluk İlkesinin ihlallerini göze çarpan bir şekilde açık hale getirmesidir."
Igor Popov

2
@DonBox Bu durumda, özyinelemeyi durdurmak için boş nesne uygulamaları yazabilirsiniz. İhtiyacınız olan şey değil, ama önemli olan, Yapıcı Enjeksiyonunun döngüleri önlemediğidir - sadece orada olduklarını açıkça ortaya koymaktadır.
Mark Seemann

66

Sınıf kurucularınızın IOC kapsayıcı döneminize bir referans olması gerektiğini düşünmüyorum. Bu, sınıfınız ve kap arasında gereksiz bir bağımlılığı temsil eder (IOC'nin kaçınmaya çalıştığı bağımlılık türü!).


+1 Bir IoC kapsayıcısına bağlı olarak, daha sonra diğer tüm sınıflarda kod
Tseng

1
Yapıcılar üzerinde arabirim parametreleri olmadan IOC'yi nasıl uygularsınız? Yayınınızı yanlış mı okuyorum?
J Hunt

@J Hunt Yorumunuzu anlamıyorum. Benim için arayüz parametreleri, bağımlılıklar için arayüzler olan parametreler, yani bağımlılık enjeksiyon kabınız başlatılırsa MyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)(arayüz parametreleri) anlamına gelir. Bu bir bağımlılık enjeksiyon kabının kendi nesnelerine enjekte etmemesi gerektiğini söyleyen @ derivation post ile ilgisiz, yani,MyClass myClass = new MyClass(this)
John Doe

25

Parametreleri geçirmenin zorluğu problem değildir. Sorun şu ki, sınıfınız çok fazla şey yapıyor ve daha fazla parçalanması gerekiyor.

Bağımlılık Enjeksiyon, özellikle tüm bağımlılıklarda artan ağrı nedeniyle, çok büyük olan sınıflar için erken bir uyarı görevi görebilir.


44
Yanılıyorsam beni düzeltin, ama bir noktada 'her şeyi birbirine yapıştırmanız' gerekiyor ve bu nedenle bunun için birkaç bağımlılıktan daha fazlasına ihtiyacınız var. Örneğin, Görünüm katmanında, onlar için şablonlar ve veriler oluştururken, çeşitli bağımlılıklardan (örn. 'Hizmetler') tüm verileri almanız ve ardından tüm bu verileri şablona ve ekrana koymanız gerekir. Web sayfamda 10 farklı 'blok' bilgi varsa, bu verileri bana sağlamak için 10 farklı sınıfa ihtiyacım var. Bu yüzden Görünüm / Şablon sınıfım için 10 bağımlılığa ihtiyacım var?
Andrew

4

Yapıcı tabanlı bağımlılık enjeksiyonu ve tüm bağımlılıklardan ne kadar karmaşık geçtiği konusunda benzer bir soruya rastladım.

Geçmişte kullandığım yaklaşımlardan biri, uygulama cephesi şablonunu bir servis katmanı kullanarak kullanmaktır. Bunun kaba bir API'sı olacaktır. Bu hizmet depolara bağlıysa, özel mülklerin ayarlayıcı enjeksiyonunu kullanır. Bu, soyut bir fabrika oluşturmayı ve depo oluşturma mantığını bir fabrikaya taşımayı gerektirir.

Açıklamalı ayrıntılı kodu burada bulabilirsiniz

Karmaşık hizmet katmanında IoC için en iyi uygulamalar


3

Bu konuyu iki kez okudum ve bence insanlar sorulanlarla değil, bildikleriyle yanıt veriyorlar.

JP'nin orijinal sorusu, bir çözümleyici ve daha sonra bir grup sınıf göndererek nesne inşa ettiği gibi görünüyor, ancak bu sınıfların / nesnelerin kendilerinin enjeksiyon için olgunlaşmış hizmetler olduğunu varsayıyoruz. Ya değillerse?

JP, DI'den yararlanmak ve enjeksiyonun bağlamsal verilerle karıştırılmasının ihtişamını arzuluyorsanız, bu kalıpların hiçbiri (veya "anti-kalıplar" olduğu varsayılır) özellikle buna değinmez. Aslında böyle bir çabada sizi destekleyecek bir paket kullanmakla ilgilidir.

Container.GetSevice<MyClass>(someObject1, someObject2)

... bu biçim nadiren desteklenir. Uygulama ile ilişkili sefil performansa eklenen bu tür bir desteği programlamanın zorluğunun, onu açık kaynak geliştiricileri için itici hale getirdiğine inanıyorum.

Ama bu yapılmalı, çünkü MyClass'es için bir fabrika oluşturabilir ve kaydedebilmeliyim ve bu fabrika sadece geçiş uğruna bir "hizmet" olmaya itilmemiş veri / girdi alabilmelidir veri. "Anti-desen" olumsuz sonuçlarla ilgiliyse, veri / model geçirmek için yapay hizmet türlerinin varlığını zorlamak kesinlikle negatiftir (sınıflarınızı bir konteynere sarmalama hissine eşit olarak. Aynı içgüdü geçerlidir).

Yine de, biraz çirkin görünseler bile yardımcı olabilecek bir çerçeve vardır. Örneğin, Ninject:

Yapıcıda ek parametrelerle Ninject kullanarak örnek oluşturma

Bu .NET içindir, popülerdir ve olması gerektiği kadar temiz değildir, ancak eminim hangi dilde kullanmayı seçeceğiniz bir şey var.


3

Konteyneri enjekte etmek, nihayetinde pişman olacağınız bir kısayoldur.

Aşırı enjeksiyon problem değildir, genellikle diğer yapısal kusurların bir belirtisidir, en önemlisi endişelerin ayrılmasıdır. Bu bir sorun değil ama birçok kaynağa sahip olabilir ve bunu düzeltmeyi zorlaştıran şey, bazen aynı anda hepsiyle uğraşmak zorunda kalacağınızdır (spagetti'yi karıştırmayı düşünün).

İşte dikkat etmeniz gereken şeylerin eksik bir listesi

Kötü Alan Adı Tasarımı (Agrega root's…. Etc)

Endişelerin zayıf ayrılması (Hizmet kompozisyonu, Komutlar, sorgular) Bkz. CQRS ve Olay Sağlama.

VEYA Haritacılar (dikkatli olun, bunlar sizi belaya sokabilir)

Modelleri ve diğer DTO'ları görüntüleyin (Asla birini tekrar kullanmayın ve minimumda tutmaya çalışın !!!!)


2

Sorun :

1) Sürekli artan parametre listesine sahip yapıcı.

2) Sınıf devralındığında (Örn RepositoryBase:) yapıcı imzasının değiştirilmesi türetilmiş sınıflarda değişikliğe neden olur.

Çözüm 1

Geçiş IoC Containerkurucusuna

Neden

  • Artık artan parametre listesi yok
  • Yapıcı imzası basitleşiyor

Neden olmasın

  • Sınıfı IoC konteynerine sıkıca bağlı yapar. (1. farklı sınıftaki IoC kapsayıcısını kullandığınız diğer projelerde bu sınıfı kullanmak istediğinizde sorunlara neden olur. 2. IoC kapsayıcısını değiştirmeye karar verdiniz)
  • Sınıfı daha az açıklayıcı yapar. (Sınıf yapıcıya gerçekten bakamazsınız ve çalışmak için neye ihtiyacı olduğunu söyleyemezsiniz.)
  • Sınıf potansiyel olarak tüm hizmetlere erişebilir.

Çözüm 2

Tüm hizmeti gruplayan ve kurucuya ileten bir sınıf oluşturun

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

Türetilmiş sınıf

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

Neden

  • Sınıfa yeni bağımlılık eklemek türetilmiş sınıfları etkilemez
  • Sınıf IoC Container'ın agnostiğidir
  • Sınıf tanımlayıcıdır (bağımlılıkları açısından). AKural olarak, hangi sınıfın bağlı olduğunu bilmek istiyorsanız , bu bilgilerA.Dependency
  • Yapıcı imzası basitleşiyor

Neden olmasın

  • ek sınıf yaratma ihtiyacı
  • hizmet kaydı karmaşık hale gelir (her birini X.Dependencyayrı ayrı kaydetmeniz gerekir )
  • Kavramsal olarak geçişle aynı IoC Container
  • ..

Çözüm 2 sadece bir ham olsa da, buna karşı sağlam bir argüman varsa, açıklayıcı yorum takdir edilecektir


1

Kullandığım yaklaşım bu

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

Burada, enjeksiyonları gerçekleştirme ve değerleri enjekte ettikten sonra yapıcıyı çalıştırma ham bir yaklaşımdır. Bu tamamen işlevsel bir programdır.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

Şu anda böyle bir hobi projesi üzerinde çalışıyorum https://github.com/Jokine/ToolProject/tree/Core


-8

Hangi bağımlılık enjeksiyon çerçevesini kullanıyorsunuz? Bunun yerine ayarlayıcı bazlı enjeksiyon kullanmayı denediniz mi?

Yapıcı tabanlı enjeksiyonun yararı, DI çerçevelerini kullanmayan Java programcıları için doğal görünmesidir. Bir sınıfı başlatmak için 5 şeye ihtiyacınız vardır, o zaman kurucunuz için 5 argümanınız olur. Olumsuz, fark ettiğiniz şeydir, çok sayıda bağımlılığınız olduğunda kullanışsız olur.

Spring ile gerekli değerleri ayarlayıcılarla geçebilir ve enjekte edilmelerini sağlamak için @ gerekli ek açıklamaları kullanabilirsiniz. Dezavantajı, başlatma kodunu yapıcıdan başka bir yönteme taşımanız ve Spring çağrısını tüm bağımlılıklardan sonra @PostConstruct ile işaretleyerek enjekte edilmeniz gerektiğidir. Diğer çerçevelerden emin değilim ama sanırım benzer bir şey yapıyorlar.

Her iki yol da çalışır, bu bir tercih meselesidir.


21
Yapıcı enjeksiyonunun nedeni, java geliştiricileri için daha doğal göründüğü için değil, bağımlılıkları açık hale getirmektir.
L-Four

8
Geç yorum, ama bu cevap beni güldürdü :)
Frederik Prijck

1
Setter bazlı enjeksiyonlar için +1. Benim sınıfta tanımlanmış hizmetler ve depolar varsa, oldukça bağımlı oldukları açıktır .. Ben büyük VB6 görünümlü yapıcılar yazmak ve yapıcı aptal atama kodu yapmak gerekmez. Gerekli alanlara bağımlılıkların ne olduğu oldukça açıktır.
Piotr Kula

2018'e göre, Spring resmi olarak makul bir varsayılan değere sahip bağımlılıklar dışında ayarlayıcı enjeksiyon kullanılmamasını önermektedir. Olduğu gibi, sınıfa bağımlılık zorunlu ise yapıcı enjeksiyonu önerilir. Bkz ctor DI vs setter üzerinde tartışma
John Doe
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.