2008-12-11 12 views
14

Patrzę na zastrzyk depresji, widzę korzyści, ale mam problemy ze składnią, którą tworzy. Mam tego przykładuZastrzyki zależne zależności

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts(IDataContext dx) 
    { 
     _dx = dx; 
    } 

    public List<Product> GetProducts() 
    { 
    return dx.GetProducts(); 
    } 
} 

Problemem jest to, że nie chcę pisać

BusinessProducts bp = new BusinessProducts(dataContextImplementation); 

będę dalej pisać

BusinessProducts bp = new BusinessProducts(); 

bo czuję pierwszą alternatywę prostu czuje unatural . Nie chcę wiedzieć, od czego zależy produkt BusinessProduct, aby uzyskać produkty, ale uważam, że czyni to mój kod bardziej nieczytelnym.

Czy są jakieś alternatywy dla tego podejścia, ponieważ chciałbym zachować moją oryginalną składnię do tworzenia obiektów, ale chciałabym nadal być w stanie udawać zależności, gdy testowanie jednostkowe lub czy jest to możliwe za pomocą tych zależnych frameworków wtryskowych?

jestem kodowania w C#, ale alternatywy z innych języków jest mile widziany

+3

Cóż, jeśli używasz bezpośrednio słowa kluczowego "nowy" zamiast żądać obiektu, w którym nie używasz wtrysku zależności. –

+4

@Pop, myślisz o strukturze DI, a nie DI wzorca. W DI wzorzec, wstrzyknięcie instancji klasy, od której jesteś zależny w konstruktorze, jest całkowicie uzasadnione. Innym sposobem jest zrobienie tego za pośrednictwem właściwości. – tvanfosson

+1

@Pop Catalin - To jest przykład DI. Instancja obiektu, który ma być wstrzyknięty, to "IDataContext", a nie "BusinessProducts". – Owen

Odpowiedz

9

Używam fabryki dla mojego kontekstu i wstrzykiwać ją, zapewniając odpowiednią wartość domyślną, jeśli dostarczona fabryka ma wartość zerową. Robię to z dwóch powodów. Po pierwsze, używam kontekstu danych jako obiektu o zasięgu pracy, więc muszę mieć możliwość tworzenia ich w razie potrzeby, a nie jednego. Po drugie, przede wszystkim używam DI do zwiększenia testowalności, a oddzielenie jest jedynie kwestią drugorzędną.

Więc moje produkty Business Class wyglądałby następująco:

public class BusinessProducts 
{ 
    private IDataContextFactory DataContextFactory { get; set; } // my interface 

    public BusinessProducts() : this(null) {} 

    public BusinessProducts(IDataContextFactory factory) 
    { 
      this.DataContext = factory ?? new BusinessProductsDataContextFactory(); 
    } 

    public void DoSomething() 
    { 
      using (DataContext dc = this.DataContextFactory().CreateDataContext()) 
      { 
      ... 
      } 
    } 

Alternatywą byłoby to zrobić publicznie własność fabrycznie ustawiane i wstrzyknąć alternatywnej fabrycznie przez ustawienie właściwości. Tak czy inaczej, jeśli chcesz zachować konstruktor null, musisz podać wartość domyślną.

3

Istnieją dwa odrębne przypadki tutaj:

w kodzie produkcyjnym będzie nigdy zapisu

new BusinessProducts(dataContextImplementation) 

ponieważ zależność wtrysk będzie normalnie tworząc pełną hierarchię obiektów dla ciebie. Jest to "wirusowy" charakter wzorców zastrzyku zależności, mają one tendencję do przejęcia pełnej kontroli nad tworzeniem usług.

W jednostkowym kodzie testu zwykle tworzysz to sam, ale dość często będziesz dostarczać fałszywy obiekt lub implementację kodu pośredniczącego dataContextImplementation. Więc zwykle będziesz wstrzykiwał obiekt, który nie ma dużej liczby kolejnych zależności.

+1

@krosenvold - Myślę, że myślisz o frameworkach DI, a nie DI. Wzorzec po prostu mówi, że robisz dokładnie to - wstrzykuje zależności za pomocą konstruktora lub ustawiaczy właściwości. Możesz użyć DI wzorca bez korzystania z frameworka DI. – tvanfosson

6

Możesz stworzyć fabrykę. Kontenery DI najlepiej nadają się do okablowania, które odbywa się w czasie instalacji - nie w czasie pracy (jak to wygląda). Fabryki można wdrażać na różne sposoby, w zależności od tego, jak należy je podłączać i ile miejsc należy z nich korzystać.

1

Generalnie sama struktura będzie miała logikę do budowania całego drzewa obiektów. Na przykład, zamiast

new SomeObjectO(diContext) 

nazwałbyś ramy takiego:

DIFramework.GetNew<SomeObjectO>(); 

lub

DIFramework.Get<SomeObject>(); 

Innym ciekawym ramy przyjrzeć się, jeśli chcesz dowiedzieć się o DI i proces to projekty Microsoft Unity i Builder obiektów.

+0

thx, to była odpowiedź wyjaśniająca. – terjetyl

+1

Dlaczego prawie wszyscy odpowiadają, dezorientują DI za pomocą schematu DI? Ramki DI używają DI, ale nie musisz używać struktury DI tylko po to, aby zaimplementować DI we wzorcu. – tvanfosson

+0

Zgadzam się z Tanimfossonem. Ta odpowiedź tylko pomogła mi zrozumieć, co robi dla ciebie struktura DI – terjetyl

6

Zwykle mam pusty konstruktor, który używa stałej instancji (lub instancji tworzonych przez IoC) i amd jeden z DI. tj.

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts() 
    { 
     _dx = new SolidDataContext(); 
    } 

    BusinessProducts(IDataContext dx) 
    { 
     _dx = dx; 
    } 
} 

W ten sposób można użyć DI do nadpisania domyślnej instancji w testowaniu jednostkowych testów.

+0

Użyłem tej techniki kilka razy do wstrzykiwania w różnych implementacjach do celów testowania jednostkowego. Jest to przydatne, jeśli nie masz pojemnika DI. – RichardOD

1

Jeśli naprawdę nie podoba ci się wstrzyknięcie tej instancji do konstruktora, możesz spróbować użyć CommonServiceLocator z ulubionym zgodnym frameworkiem wtrysku .NET depedency. Pozwoliłoby to na pisanie kodu:

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts() 
    { 
     _dx = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IDataContext>(); 
    } 

    public List<Product> GetProducts() 
    { 
    return dx.GetProducts(); 
    } 
} 

Jednakże proszę pamiętaj, że to nie jest to, co większość ludzi będzie oczekiwać, gdy wiedzą, że używasz ramy wstrzykiwania zależności. Myślę, że znacznie powszechniej jest używać szkieletu zależności wtrysku i umożliwiającego tworzenie dla niego wszystkich obiektów.

BusinessProducts bp = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<BusinessProducts>(); 

Jeśli chcesz uniknąć ścieżki ramowej zastrzyków zależnych, prawdopodobnie najlepszym rozwiązaniem jest skorzystanie z fabryki.

1

Jest to technika zwana DI biedaka, który wygląda jak ten

public class BusinessProducts 
{ 
    IDataContext _dx; 

    BusinessProducts() : this(new DataContext()) {} 

    BusinessProducts(IDataContext dx) 
    { 
     _dx = dx; 
    } 

    public List<Product> GetProducts() 
    { 
    return dx.GetProducts(); 
    } 
} 

To nie jest idealny, ponieważ wiąże Cię do realizacji, ale jego dobry krok w kierunku kodu oddzielonej od produkcji. jest to podobne do @tvanfosson, ale znacznie prostsze.

I druga zalecenie dla Windsor

1

mój kod będzie odwoływać Microsoft Jedności, ale jestem pewien, że to jest dość zastosowanie do wszystkich ram DI. Jeśli używasz DI poprawnie nigdy nie musisz wywoływać nowego BusinessObject (new dataContext), stowarzyszenie DI poradzi sobie z tym wszystkim.

Mój przykład będzie trochę dłuższy, ponieważ wkleję w nim kod, którego używam do uruchamiania witryny z podglądem modelu w pełni DI załadowanej przez Unity. (Jeśli chcesz w pełni źródło sprawdzeniu mojego bloga i pobrać go z mojego serwera Assembla SVN)

Załaduj pojemnik (może być w kodzie jak wolę lub za pomocą konfiguracji)

protected void Application_Start(object sender, EventArgs e) 
{ 
    Application.GetContainer() 
     // presenters/controllers are per request     
     .RegisterType<IEmployeeController, EmployeeController>(new ContextLifetimeManager<IEmployeeController>()) 

     //Data Providers are Per session     
     .RegisterType<IEmployeeDataProvider, EmployeeDataProvider>(new SessionLifetimeManager<IEmployeeDataProvider>()) 

     //Session Factory is life time 
     .RegisterType<INHibernateSessionManager, NHibernateSessionManager>(new ContainerControlledLifetimeManager()); 
} 

klienta HTTP połączenia modułu Metoda Unity BuildUp dla każdej strony podczas wywołania OnPreRequest.

private static void OnPreRequestHandlerExecute(object sender, EventArgs e) 
{ 
    var handler = HttpContext.Current.Handler; 
    HttpContext.Current.Application.GetContainer().BuildUp(handler.GetType(), handler); 

    // User Controls are ready to be built up after the page initialization is complete 
    var page = HttpContext.Current.Handler as Page; 
    if (page != null) 
    { 
     page.InitComplete += OnPageInitComplete; 
    } 
} 

pojemnik Page prezenter ozdobione [Zależność] przypisują

public partial class Employees : Page, IEmployeeView 
{ 
    private EmployeePresenter _presenter; 

    [Dependency] 
    public EmployeePresenter Presenter 
    { 
     set 
     { 
      _presenter = value; 
      _presenter.View = this; 
     } 
    } 
} 

Presenter metodą InjectionConstructor

public class EmployeePresenter : Presenter<IEmployeeView> 
{ 
    private readonly IEmployeeController _controller; 

    [InjectionConstructor] 
    } 
    public EmployeePresenter(IEmployeeController controller) 
    { 
     _controller = controller; 
} 

Controller następuje garnitur

public class EmployeeController : IEmployeeController 
{ 
    private readonly IEmployeeDataProvider _provider; 

    [InjectionConstructor] 
    public EmployeeController(IEmployeeDataProvider DataProvider) 
    { 
     _provider = DataProvider; 
    } 
} 

samo z pro vider

public class EmployeeController : IEmployeeController 
{ 
    private readonly IEmployeeDataProvider _provider; 

    [InjectionConstructor] 
    public EmployeeController(IEmployeeDataProvider DataProvider) 
    { 
     _provider = DataProvider; 
    } 
} 

Wreszcie menedżer sesji, który zawiera tylko zwykłego konstruktora.

public class NHibernateSessionManager : INHibernateSessionManager 
{ 
    private readonly ISessionFactory _sessionFactory; 

    public NHibernateSessionManager() 
    {    
     _sessionFactory = GetSessionFactory(); 
    } 
} 

Więc co się dzieje, gdy żądanie strony jest uruchamiany gromadzeniu metoda() jest wywoływana na stronie przez HttpModule. Unity następnie widzi właściwość oznaczoną atrybutem Dependency i sprawdzi kontener, aby sprawdzić, czy w środku istnieje obiekt EmployeePresenter.

Ponieważ nie ma takiego obiektu w pojemniku, wówczas spróbuje utworzyć EmployeePresenter. Po sprawdzeniu, aby utworzyć klasę, którą widzi wewnątrz prezentera, wymaga konstruktora, który potrzebuje wtryskiwanego IEmployeeController. Ponieważ kontener faktycznie ma menedżera dla kontrolera, zobaczy, czy jego instancja istnieje w kontenerze, który na początku żądania strony nie istnieje, a więc przejdzie do utworzenia instancji kontrolera.

Unity zobaczy, że kontroler wymaga wtryskiwacza IEmployeeDataProvider i będzie kontynuował ten proces, aż w końcu dotrze do punktu, w którym dostawca potrzebuje wstrzykniętego menedżera sesji. Ponieważ menedżer sesji nie potrzebuje już wtrysku, Unity utworzy instancję menedżera sesji, zapisze go w kontenerze, podając ContainerLifeTimeManager, wstrzyknie go do dostawcy i zapisze to wystąpienie, i tak dalej, aż do zakończenia tworzenia Zależność EmployeePresenter dla strony.

4

Twoje uczucia, mimo że są prawidłowe, są niewłaściwie umieszczone.

Wzór Dependency Injection jest bezpośrednim zastosowaniem zasady Inversion of Control.

Oznacza to, że zamiast klasy kontrolującej wystąpienia innych klas, które zużywa, ta relacja jest odwrócona odwrócona, a zależności są do niej dostarczane.

Jako takie, twoje klasy w naturalny sposób eksponują swoje zależności za pomocą argumentów lub właściwości konstruktora. Pokazywanie pogardy dla tej struktury mówi, że naprawdę nie zinterpretowałeś tego schematu.