Сохранение использования DI-контейнера в корне композиции в Silverlight и MVVM - PullRequest
13 голосов
/ 03 февраля 2010

Мне не совсем понятно, как я могу проектировать, поэтому я сохраняю ссылку на DI-контейнер в корне композиции для приложения Silverlight + MVVM.

У меня есть следующий простой сценарий использования: есть главное представление (возможно, список элементов) и действие, чтобы открыть представление редактирования для одного отдельного элемента. Таким образом, основной вид должен создавать и отображать вид редактирования, когда пользователь выполняет действие (например, нажимает какую-то кнопку).

Для этого у меня есть следующий код:

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

Затем для каждого представления, которое мне нужно создать, у меня есть абстрактная фабрика, например,

public interface ISomeViewFactory
{
   IView CreateView();
}

Эта фабрика затем объявляется зависимостью от "родительской" модели представления, например так:

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

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

Так что пока все хорошо, DI-контейнера не видно :). Теперь идет реализация ISomeViewFactory:

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

"????" Отчасти это моя проблема, потому что модель представления для представления должна быть разрешена из DI-контейнера, чтобы в него вставлялись его зависимости. Чего я не знаю, так это как я могу это сделать, не имея зависимости от DI-контейнера нигде, кроме корня композиции.

Одним из возможных решений было бы наличие зависимости от модели представления, внедряемой в фабрику, например:

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

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

Хотя это работает, проблема заключается в том, что, поскольку весь граф объектов подключен «статически» (т. Е. Модель «родительского» представления получит экземпляр SomeViewFactory, который получит экземпляр SomeViewModel, и они будут жить до тех пор, пока существует «родительская» модель представления), реализация модели с внедренным представлением находится в состоянии, и если пользователь дважды открывает дочернее представление, во второй раз модель представления будет того же экземпляра и будет иметь прежнее состояние. Я думаю, я мог бы обойти это с помощью метода «Initialize» или чего-то подобного, но это не совсем пахнет.

Другое решение может заключаться в том, чтобы обернуть DI-контейнер и заставить фабрики зависеть от оболочки, но это все равно будет DI-контейнер "замаскированный" там:)

ps: мое текущее решение состоит в том, что фабрики знают о DI-контейнере, и только они и корень композиции имеют эту зависимость.

1 Ответ

7 голосов
/ 03 февраля 2010

Чтобы оставаться как можно ближе к вашему примеру кода, вы можете ввести еще один уровень косвенности в виде IViewPopulator:

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

Теперь вы можете реализовать свой SomeViewFactory следующим образом:

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;
    }
}

Это разделяет создание Views и совокупность ViewModels, придерживаясь принципа единой ответственности . В определенной степени это также пример агрегации услуг .

Теперь вы можете реализовать IViewPopulator как конкретный тип, который принимает нормальные зависимости:

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;
    }
}

Вероятно, существуют и другие способы моделирования отношений между IView и IViewModel, но приведенное выше представляет одно из возможных решений.

Ключ заключается в том, чтобы продолжать извлекать абстракции до тех пор, пока у каждого не будет четко определенная ответственность. Это упражнение на самом деле не о том, чтобы сделать код независимым от контейнера, а в конечном итоге о соблюдении принципов SOLID.

...