DI konteyneri aracılığıyla oluşturulan nesneleri başlatmak için bir model var


147

Nesnelerimin oluşturulmasını yönetmek için Unity'yi almaya çalışıyorum ve çalışma zamanına kadar bilinmeyen bazı başlatma parametrelerine sahip olmak istiyorum:

Şu anda bunu yapmanın tek yolu arayüzde bir Init yöntemine sahip olmak.

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

Sonra onu kullanmak için (Unity'de) şunu yapardım:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

Bu senaryoda runTimeParam, kullanıcı girdisine göre çalışma zamanında param belirlenir. Buradaki önemsiz durum basitçe değerini döndürür, runTimeParamancak gerçekte parametre dosya adı gibi bir şey olacaktır ve başlatma yöntemi dosya ile bir şeyler yapacaktır.

Bu, Initializeyöntemin arayüzde mevcut olması ve birden çok kez çağrılabilmesi gibi bir dizi sorun yaratır . Uygulamada bir bayrak ayarlamak ve tekrarlanan çağrıda istisna atmak çok Initializehantal görünüyor.

Arayüzümü çözdüğüm noktada, uygulamasına ilişkin hiçbir şey bilmek istemiyorum IMyIntf. Yine de istediğim, bu arayüzün belirli bir seferlik başlatma parametrelerine ihtiyaç duyduğu bilgisidir. Bu bilgilerle arayüze bir şekilde açıklama eklemenin (öznitelikler?) Ve nesne oluşturulduğunda bunları çerçeveye aktarmanın bir yolu var mı?

Düzenleme: Arayüzü biraz daha açıkladı.


9
DI konteyneri kullanma noktasını kaçırıyorsunuz. Bağımlılıkların sizin için çözülmesi gerekiyor.
Pierreten

İhtiyaç duyduğunuz parametreleri nereden alıyorsunuz? (yapılandırma dosyası, db, ??)
Jaime

runTimeParambir kullanıcı girdisine göre çalışma zamanında belirlenen bir bağımlılıktır. Bunun alternatifi onu iki arayüze bölmek mi olmalı - biri başlangıç ​​için, diğeri değerleri depolamak için?
Igor Zevaka

IoC'de bağımlılık, genellikle IoC başlatma aşamasında belirlenebilen diğer ref türü sınıflara veya nesnelere bağımlılığı ifade eder. Sınıfınızın çalışması için yalnızca bazı değerlere ihtiyacı varsa, sınıfınızdaki Initialize () yöntemi burada kullanışlı hale gelir.
The Light

Demek istediğim, uygulamanızda bu yaklaşımın uygulanabileceği 100 sınıf olduğunu hayal edin; o zaman sınıflarınız için fazladan 100 fabrika sınıfı + 100 arayüz oluşturmanız gerekecek ve yalnızca Initialize () yöntemini kullanıyor olsaydınız işten kurtulabilirsiniz.
The Light

Yanıtlar:


277

Belirli bir bağımlılık oluşturmak için bir çalışma zamanı değerine ihtiyaç duyduğunuz her yerde, Abstract Factory çözümdür.

Arayüzlerde Yöntemleri Başlatmak Sızdıran Soyutlama kokuyor .

Sizin durumunuzda, IMyIntfarayüzü nasıl kullanmanız gerektiğine göre modellemeniz gerektiğini söyleyebilirim - uygulamalarını nasıl oluşturmayı düşündüğünüz değil. Bu bir uygulama detayıdır.

Bu nedenle, arayüz basitçe şöyle olmalıdır:

public interface IMyIntf
{
    string RunTimeParam { get; }
}

Şimdi Soyut Fabrikayı tanımlayın:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

Şimdi IMyIntfFactorybunun IMyIntfgibi somut örnekler oluşturan somut bir uygulama oluşturabilirsiniz :

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

Bunun , anahtar kelimeyi kullanarak sınıfın değişmezlerini korumamıza nasıl izin verdiğine dikkat edin readonly. Kokulu Başlatma yöntemlerine gerek yoktur.

Bir IMyIntfFactoryuygulama şu kadar basit olabilir:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

Bir IMyIntförneğe ihtiyaç duyduğunuz tüm tüketicilerinizde , bunu Constructor InjectionIMyIntfFactory yoluyla talep ederek bağımlılık kazanırsınız .

IMyIntfFactoryDoğru bir şekilde kaydettirirseniz , tuzuna değecek herhangi bir DI Container, bir örneği sizin için otomatik olarak bağlayabilir.


13
Sorun, bir yöntemin (Initialize gibi) API'nizin bir parçası olması, ancak yapıcının olmamasıdır. blog.ploeh.dk/2011/02/28/InterfacesAreAccessModifiers.aspx
Mark Seemann

13
Ayrıca, bir Initialize yöntemi Temporal Coupling'i gösterir: blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling.aspx
Mark Seemann

2
@Darlene Sen bölümünde 8.3.6 de açıklandığı gibi, bir lazily başlatıldı Decorator kullanmak mümkün olabilir kitabımda . Ayrıca Büyük Nesne Grafikleri Önden Sunumumda benzer bir örnek veriyorum .
Mark Seemann 13

2
@Mark Eğer fabrikanın MyIntfuygulama oluşturması daha fazlasını gerektiriyorsa runTimeParam(okuyun: birinin bir IoC tarafından çözülmesini isteyeceği diğer hizmetler), o zaman fabrikanızda bu bağımlılıkları çözmekle karşı karşıya kalırsınız. @PhilSandler'in bunu çözmek için bu bağımlılıkları fabrikanın kurucusuna geçirme cevabını beğendim - bu sizin de üstlenmeniz mi?
Jeff

2
Ayrıca harika şeyler, ama bu diğer soruya verdiğiniz cevabın gerçekten benim açımdan var.
Jeff

15

Genellikle bu durumla karşılaştığınızda, tasarımınızı yeniden gözden geçirmeniz ve durum bilgisi olan / veri nesnelerinizi saf hizmetlerinizle karıştırıp karıştırmadığınızı belirlemeniz gerekir. Çoğu durumda (tümü değil), bu iki tür nesneyi ayrı tutmak isteyeceksiniz.

Yapıcıda içeriğe özgü bir parametreye ihtiyacınız varsa, bir seçenek oluşturucu aracılığıyla hizmet bağımlılıklarınızı çözen ve çalışma zamanı parametrenizi Create () yönteminin bir parametresi olarak alan bir fabrika oluşturmaktır (veya Generate ( ), Build () veya fabrika yöntemlerinizi adlandırın).

Ayarlayıcılara veya Initialize () yöntemine sahip olmak genellikle kötü tasarım olarak düşünülür, çünkü onları çağırmayı "hatırlamanız" ve uygulamanızın durumunu çok fazla açmamalarını sağlamanız gerekir (örn. -calling initialize veya setter?).


5

Model nesnelerine dayalı olarak dinamik olarak ViewModel nesneleri oluşturduğum ortamlarda da bu duruma birkaç kez rastladım (bu diğer Stackoverflow gönderisinde gerçekten iyi bir şekilde özetlenmiştir ).

Arayüzlere dayalı dinamik olarak fabrikalar oluşturmanıza izin veren Ninject uzantısının nasıl olmasını beğendim :

Bind<IMyFactory>().ToFactory();

Doğrudan Unity'de benzer bir işlevsellik bulamadım ; bu yüzden IUnityContainer'a kendi uzantımı yazdım, bu da temelde bir tür hiyerarşisinden farklı bir tür hiyerarşisine eşlenen mevcut nesnelerden gelen verilere dayanarak yeni nesneler oluşturacak fabrikaları kaydetmenize olanak tanıyor: UnityMappingFactory @ GitHub

Basitlik ve okunabilirlik amacıyla, tek tek fabrika sınıflarını veya arayüzlerini (gerçek zaman tasarrufu) bildirmeden eşlemeleri doğrudan belirlemenize olanak tanıyan bir uzantı elde ettim. Eşlemeleri, normal önyükleme işlemi sırasında sınıfları kaydettiğiniz yere eklemeniz yeterlidir ...

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

Ardından, CI için yapıcıda eşleme fabrikası arayüzünü bildirir ve bunun Create () yöntemini kullanırsınız ...

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

Ek bir bonus olarak, eşlenen sınıfların yapıcısındaki herhangi bir ek bağımlılık da nesne oluşturma sırasında çözülecektir.

Açıkçası, bu her sorunu çözmeyecek ama şimdiye kadar bana oldukça iyi hizmet etti, bu yüzden paylaşmam gerektiğini düşündüm. GitHub'daki projenin sitesinde daha fazla belge var.


1

Belirli Unity terminolojisiyle cevap veremem ama görünüşe göre bağımlılık enjeksiyonunu öğreniyorsunuz. Öyleyse, Ninject için kısa, anlaşılır ve bilgi dolu kullanıcı kılavuzunu okumanızı tavsiye ederim .

Bu, DI kullanırken sahip olduğunuz çeşitli seçenekler ve yol boyunca karşılaşacağınız belirli sorunları nasıl hesaba katacağınız konusunda size yol gösterecektir. Sizin durumunuzda, nesnelerinizi somutlaştırmak için büyük olasılıkla DI kapsayıcısını kullanmak ve bu nesnenin kurucu aracılığıyla bağımlılıklarının her birine bir referans almasını sağlamak isteyeceksiniz.

İzlenecek yol ayrıca, çalışma zamanında bunları ayırt etmek için öznitelikleri kullanarak yöntemleri, özellikleri ve hatta parametreleri nasıl ekleyeceğinizi de ayrıntılarıyla anlatır.

Ninject'i kullanmasanız bile, izlenecek yol size amacınıza uygun işlevsellik kavramlarını ve terminolojisini verecektir ve bu bilgiyi Unity veya diğer DI çerçevelerine eşleştirebilmelisiniz (veya Ninject'i denemeye ikna edebilmelisiniz) .


Bunun için teşekkürler. Aslında DI çerçevelerini değerlendiriyorum ve NInject benim bir sonraki çerçevem ​​olacaktı.
Igor Zevaka

@johann: sağlayıcılar? github.com/ninject/ninject/wiki/…
anthony

1

Sanırım çözdüm ve oldukça sağlıklı hissettiriyor, bu yüzden yarısı doğru olmalı :))

Ben bölünmüş IMyIntfbir "getter" ve "setter" arayüzleri içine. Yani:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

Ardından uygulama:

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize()hala birden çok kez çağrılabilir, ancak Servis Bulucu paradigmasının bitlerini kullanarak onu oldukça güzel bir şekilde tamamlayabiliriz IMyIntfSetter, böylece neredeyse farklı olan bir dahili arayüzdür IMyIntf.


13
Leaky Abstraction olan Initialize yöntemine dayandığı için bu özellikle iyi bir çözüm değildir. Btw, bu Servis Bulucuya benzemiyor, daha çok Arayüz Enjeksiyonuna benziyor. Her durumda, daha iyi bir çözüm için cevabıma bakın.
Mark Seemann
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.