13

Nie jest dla mnie jasne, w jaki sposób mogę zaprojektować, więc zachowuję odniesienie do kontenera DI w katalogu głównym kompozycji dla aplikacji Silverlight + MVVM.Utrzymywanie użycia kontenera DI w głównym składzie w Silverlight i MVVM

Mam następujący prosty scenariusz użycia: istnieje widok główny (być może lista elementów) i akcja otwierająca widok edycji dla pojedynczego elementu. Główny widok musi więc tworzyć i pokazywać widok edycji, gdy użytkownik wykona akcję (np. Kliknie jakiś przycisk).

Do tego mam następujący kod:

public interface IView 
{ 
    IViewModel ViewModel {get; set;} 
} 

Następnie dla każdego widoku, że muszę być w stanie stworzyć mam Abstract Factory, podobnie jak

public interface ISomeViewFactory 
{ 
    IView CreateView(); 
} 

Fabryka ta jest następnie zadeklarowano zależność modelu widoku "macierzystego", tak jak to:

public class SomeParentViewModel 
{ 
    public SomeParentViewModel(ISomeViewFactory viewFactory) 
    { 
     // store it 
    } 

    private void OnSomeUserAction() 
    { 
     IView view = viewFactory.CreateView(); 
     dialogService.ShowDialog(view); 
    }  
} 

Wszystko jest dobrze, dopóki nie pojawi się tu DI -kontener w zasięgu wzroku :). Teraz przychodzi implementacja ISomeViewFactory:

public class SomeViewFactory : ISomeViewFactory 
{ 
    public IView CreateView() 
    { 
     IView view = new SomeView(); 
     view.ViewModel = ???? 
    } 
} 

"????" część jest moim problemem, ponieważ model widoku dla widoku musi zostać rozwiązany z kontenera DI, aby uzyskać wstrzyknięte jego zależności. To, czego nie wiem, to jak mogę to zrobić bez zależności od kontenera DI w dowolnym miejscu z wyjątkiem katalogu głównego kompozycji.

Możliwym rozwiązaniem byłoby mieć albo zależność od modelu widoku, który zostaje wstrzyknięty do fabryki, tak jak poniżej:

public class SomeViewFactory : ISomeViewFactory 
{ 
    public SomeViewFactory(ISomeViewModel viewModel) 
    { 
     // store it 
    } 

    public IView CreateView() 
    { 
     IView view = new SomeView(); 
     view.ViewModel = viewModel; 
    } 
} 

Chociaż to działa, to ma problem, ponieważ wykres cały obiekt jest okablowane "statycznie" (tzn. model widoku "rodzica" otrzyma instancję SomeViewFactory, która otrzyma instancję SomeViewModel i będą one działać tak długo, jak długo będzie istniał model widoku macierzystego), implementacja modelu widoku wstrzykniętego jest stanowy i jeśli użytkownik otwiera widok podrzędny dwa razy, drugi raz model widoku będzie tą samą instancją i będzie miał stan sprzed. Sądzę, że mógłbym obejść to z metodą "Initialize" lub czymś podobnym, ale nie pachnie całkiem poprawnie.

Innym rozwiązaniem mogłoby być owinąć DI-kontener i mieć fabryki zależy na opakowaniu, ale ja nadal być DI-kontener „w przebraniu” tam :)

PS: moje obecne rozwiązanie jest że fabryki wiedzą o kontenerze DI, i tylko one i główny katalog mają tę zależność.

Odpowiedz

7

Aby pozostać jak najbliżej kodzie przykład jak to możliwe, można wprowadzić kolejny poziom pośredni w postaci IViewPopulator:

public interface IViewPopulator 
{ 
    void Populate(IView view); 
} 

Teraz można wdrożyć SomeViewFactory tak:

public class SomeViewFactory : ISomeViewFactory 
{ 
    private readonly IViewPopulator populator; 

    public SomeViewFactory(IViewPopulator populator) 
    { 
     if (populator == null) 
     { 
      throw new ArgumentNullException("populator"); 
     } 

     this.populator = populator; 
    } 

    public IView CreateView() 
    { 
     IView view = new SomeView(); 
     this.populator.Populate(view); 
     return view; 
    } 
} 

Oddziela to tworzenie widoków i populację ViewModels, przylegając do Single Responsibility Principle. W pewnym stopniu jest to również przykład Service Aggregation.

Teraz można wdrożyć IViewPopulator jako konkretnego typu, który trwa normalne zależności:

public class SomeViewPopulator : IViewPopulator 
{ 
    private readonly IDependency dep; 

    public SomeViewPopulator(IDependency dep) 
    { 
     if (dep == null) 
     { 
      throw new ArgumentNullException("dep"); 
     } 

     this.dep = dep; 
    } 

    public void Populate(IView view) 
    { 
     var vm = // Perhaps use this.dep to create an instance of IViewModel... 
     view.ViewModel = vm; 
    } 
} 

Istnieje prawdopodobnie inne sposoby można modelować relacje między iVIEW i IViewModel, ale powyżej przedstawia jedną z możliwych rozwiązanie.

Kluczem jest ciągłe wydobywanie abstrakcji, dopóki każdy nie będzie miał ściśle określonej odpowiedzialności. W tym ćwiczeniu nie chodzi o to, by w ogóle kodować agnostyk, ale ostatecznie o przestrzeganie zasad SOLID.

+1

Wielkie dzięki. Chociaż twoja implementacja jest krystalicznie czysta, to, co faktycznie zrobiłem, było właśnie zadeklarowanie zależności dla modelu widoku w moich fabrykach, ponieważ są one naprawdę bardzo proste i czułem, że posiadanie czegoś takiego, jak populator, jest dla mnie przesadzone. Mimo to, z punktu widzenia SOLID, a szczególnie, gdy sprawy komplikują się, oddzielając odpowiedzialność, tak jak to robiłeś, ma to wiele sensu. Świetne rzeczy :) –

+0

Cieszę się, że pomogłem :) –