8

Jestem przyzwyczajony do IoC/DI w aplikacjach internetowych - głównie Ninject z MVC3. Mój kontroler jest stworzony dla mnie, wypełniony wszystkimi zależnościami w miejscu, podległościami itp.Właściwy sposób wykonywania iniekcji Dependency w aplikacji klienta Windows (WPF)

Rzeczy są jednak różne w grubej aplikacji klienckiej. Muszę tworzyć własne obiekty lub muszę powrócić do podejścia opartego na stylu lokalizatora usług, w którym pytam jądro (prawdopodobnie za pomocą jakiegoś interfejsu, aby umożliwić testowalność), aby dać mi obiekt kompletny z zależnościami.

Widziałem jednak kilka miejsc, w których Lokalizator Usług został opisany jako wzorzec przeciwwskazania.

Moje pytanie brzmi - czy jeśli chcę korzystać z aplikacji Ninject w mojej grubej aplikacji klienckiej, czy istnieje lepszy/bardziej odpowiedni sposób na uzyskanie tego wszystkiego?

  • Testability
  • Właściwa DI/IoC
  • najmniejszą ilością sprzęgła możliwym

Należy pamiętać, że nie jestem tylko mówić o MVVM tutaj i coraz Przeglądanie modeli w widokach. Jest to w szczególności spowodowane koniecznością dostarczenia obiektu typu repozytorium z jądra, a następnie pobierania elementów z tego repozytorium z wprowadzoną funkcjonalnością (dane oczywiście pochodzą z bazy danych, ale również potrzebują niektórych obiektów jako parametrów w zależności od stanu świata, a Ninject wie, jak to zapewnić). Czy mogę w jakiś sposób to zrobić bez pozostawienia obu repozytoriów i podmiotów jako niepoprawnych mes?

Jeśli coś jest niejasne, daj mi znać. Dzięki!

EDIT 14 lipca

Jestem pewien, że te dwie odpowiedzi przewidziane są prawdopodobnie poprawne. Jednak każde włókno mojego ciała walczy z tą zmianą; Część z nich jest prawdopodobnie spowodowana brakiem wiedzy, ale jest też jeden konkretny powód, dla którego mam problem z dostrzeganiem elegancji tego sposobu robienia rzeczy;

Nie wyjaśniłem tego wystarczająco dobrze w pierwotnym pytaniu, ale chodzi o to, że piszę bibliotekę, która będzie używana przez kilka (4-5 początkowo, może później) aplikacji klienckich WPF. Wszystkie te aplikacje działają na tym samym modelu domenowym itd., Więc przechowywanie wszystkiego w jednej bibliotece jest jedynym sposobem na utrzymanie DRY. Istnieje jednak szansa, że ​​klienci tego systemu będą pisać własnych klientów - i chcę, aby mieli prostą, czystą bibliotekę do rozmowy. Nie chcę zmusić ich do użycia DI w ich składzie głównym (używając terminu jak Mark Seeman w jego książce) - ponieważ to HUGELY komplikuje rzeczy w porównaniu do nich właśnie wymyślanie MyCrazySystemAdapter() i używanie tego.

Teraz, MyCrazySystemAdapter (nazwa wybrana, ponieważ wiem, że ludzie nie zgadzają się ze mną tutaj) musi być skomponowany przez podskładniki i złożony razem przy użyciu DI. Sam MyCrazySystemAdapter nie powinien być wstrzykiwany. Jest to jedyny interfejs, z którego klienci muszą rozmawiać z systemem. Tak więc klient szczęśliwie powinien dostać jeden z nich, DI dzieje się jak magia za kulisami, a obiekt składa się z wielu różnych obiektów przy użyciu najlepszych praktyk i zasad.

Rozumiem, że będzie to kontrowersyjny sposób na zrobienie czegoś. Jednak znam także ludzi, którzy będą klientami tego API.Jeśli zobaczą, że muszą uczyć się i podłączać system DI, i tworzyć z wyprzedzeniem swoją całą strukturę obiektów w punkcie wejścia do aplikacji (Composition Root), zamiast tworzyć nowy obiekt, dadzą mi środkowy palec i Zanieczyszczaj bazy danych bezpośrednio i zepsuj rzeczy tak, jak trudno sobie wyobrazić.

TL; DR: Dostarczenie odpowiednio zorganizowanego interfejsu API jest zbyt trudne dla klienta. Moje API musi dostarczyć pojedynczy obiekt - zbudowany za kulisami przy użyciu DI i odpowiednich praktyk - z których mogą korzystać. Prawdziwy świat czasami trąca pragnienie budowania wszystkiego wstecz, aby pozostać wiernym wzorcom i praktykom.

Odpowiedz

5

Proponuję przyjrzeć się ram MVVM jak Caliburn. Zapewniają integrację z kontenerami IoC.


Zasadniczo powinieneś zbudować kompletną aplikację w swojej app.xaml. Jeśli niektóre części muszą zostać utworzone później, ponieważ nie znasz jeszcze wszystkiego, aby je utworzyć podczas uruchamiania, wstrzyknij fabrykę jako interfejs (patrz poniżej) lub Func (patrz Does Ninject support Func (auto generated factory)?) do klasy, która musi utworzyć tę instancję. Obie będą obsługiwane natywnie w następnym wydaniu Ninject.

np.

public interface IFooFactory { IFoo CreateFoo(); } 
public class FooFactory : IFooFactory 
{ 
    private IKernel kernel; 
    FooFactory(IKernel kernel) 
    { 
     this.kernel = kernel; 
    } 

    public IFoo CreateFoo() 
    { 
     this.kernel.Get<IFoo>(); 
    } 
} 

Należy pamiętać, że wdrożenie fabryczne należy logicznie do konfiguracji kontenera, a nie do wdrożenia klas biznesowych.

1

Nie wiem nic na temat WPF lub MVVM, ale twoje pytanie dotyczy w zasadzie sposobu wydobycia rzeczy z pojemnika bez użycia Lokalizatora Usług (lub kontenera bezpośrednio) wszędzie, prawda?
Jeśli tak, mogę pokazać przykład.

Chodzi o to, że zamiast tego używasz fabryki, która wewnętrznie używa kontenera. W ten sposób używasz kontenera tylko w jednym miejscu.

Uwaga: Użyję przykład z WinForms i nie związany z konkretnym pojemnikiem (bo, jak już mówiłem, nie wiem WPF ... i Używam Windsor Castle zamiast NInject), ale ponieważ twoje podstawowe pytanie nie jest specyficznie związane z WPF/NInject, powinno ci być łatwo "przenieść" moją odpowiedź na WFP/NInject.

Fabryka wygląda następująco:

public class Factory : IFactory 
{ 
    private readonly IContainer container; 

    public Factory(IContainer container) 
    { 
     this.container = container; 
    } 

    public T GetStuff<T>() 
    { 
     return (T)container.Resolve<T>(); 
    } 
} 

Główną formą aplikacji dostaje tę fabrykę poprzez wstrzyknięcie konstruktora:

public partial class MainForm : Form 
{ 
    private readonly IFactory factory; 

    public MainForm(IFactory factory) 
    { 
     this.factory = factory; 
     InitializeComponent(); // or whatever needs to be done in a WPF form 
    } 
} 

Pojemnik jest inicjowana podczas uruchamiania aplikacji, a głównym Formularz jest rozwiązany (więc dostaje fabrykę za pomocą wtrysku konstruktora).

static class Program 
{ 
    static void Main() 
    { 
     var container = new Container(); 
     container.Register<MainForm>(); 
     container.Register<IFactory, Factory>(); 
     container.Register<IYourRepository, YourRepository>(); 

     Application.Run(container.Resolve<MainForm>()); 
    } 
} 

Teraz główną formą mogą korzystać z fabryki, aby uzyskać takie rzeczy repozytorium z pojemnika:

var repo = this.factory.GetStuff<IYourRepository>(); 
repo.DoStuff(); 

Jeśli masz więcej form i chcą korzystać z fabryki z również tam, ty Wystarczy wprowadzić fabrykę do tych formularzy, jak w głównej formie, zarejestrować dodatkowe formularze przy starcie i otworzyć je z głównego formularza z fabryki.

Czy to chciałeś wiedzieć?


EDIT:
Ruben, oczywiście masz rację. Mój błąd.
Cała moja odpowiedź była dawnym przykładem, że gdzieś leżałem, ale spieszyłem się, kiedy napisałem swoją odpowiedź i nie czytałem wystarczająco dokładnie kontekstu mojego starego przykładu.

Mój stary przykład obejmował posiadanie głównego formularza, z którego można otworzyć dowolną inną formę aplikacji. To jest do czego była fabryka, więc nie trzeba wstrzykiwać co innej innej postaci za pomocą wstrzyknięcia konstruktora do głównego formularza.
Zamiast tego można skorzystać z fabryki, aby otworzyć każdą nową postać:

var form = this.factory.GetStuff<IAnotherForm>(); 
form.Show(); 

Oczywiście nie trzeba fabrykę, aby dostać się do repozytorium z postaci, jak długo repozytorium jest przekazywana do formularza przez wstrzyknięcie konstruktora.
Jeśli aplikacja składa się z zaledwie kilku formach, nie trzeba fabrykę w ogóle, można po prostu przejść formy poprzez wstrzyknięcie konstruktora, a także:

public partial class MainForm : Form 
{ 
    private readonly IAnotherForm form; 

    // pass AnotherForm via constructor injection 
    public MainForm(IAnotherForm form) 
    { 
     this.form = form; 
     InitializeComponent(); // or whatever needs to be done in a WPF form 
    } 

    // open AnotherForm 
    private void Button1_Click(object sender, EventArgs e) 
    { 
     this.form.Show(); 
    } 
} 

public partial class AnotherForm : Form 
{ 
    private readonly IRepository repo; 

    // pass the repository via constructor injection 
    public AnotherForm(IRepository repo) 
    { 
     this.repo= repo; 
     InitializeComponent(); // or whatever needs to be done in a WPF form 

     // use the repository 
     this.repo.DoStuff(); 
    } 
} 
+4

-1 Twoje 'GetStuff()' jest lokalizatorem usług w sukience. Nie rób tego. (Nie ma nic złego w reszcie twojej odpowiedzi, ale SLness sprawia, że ​​to nie w porządku) –

+0

Ruben, masz rację. Poprawiłem moją odpowiedź! –

+0

Jeszcze zanim ujrzałem odpowiedź Remo'a (co uważam za poprawne podejście), miałem zamiar zasugerować wstrzyknięcie fabryk (jako Funcs gdzie to odpowiednie a la http://stackoverflow.com/questions/4840157/does-ninject-support- func-auto-generated-factory/4851885 # 4851885) –

Powiązane problemy