используя DependencyInjection на верхнем уровне, как передать услуги по архитектуре? - PullRequest
3 голосов
/ 24 июня 2011

Используя IOC-контейнер, такой как Unity, AutoFac или другие, вы должны зарегистрировать и разрешить IInterface, чтобы получить экземпляр.Это вы делаете в классе приложения корень всех.

После того, как вы выполнили «Регистрация / разрешение», я создаю свой MainController и передаю им ВСЕ разрешенные службы, такие как:

protected void Application_Start(object sender, EventArgs e)
{
    var builder = new ContainerBuilder();

    builder.Register<IUserService1, UserService1>();
    builder.Register<IUserService2, UserService2>();
    builder.Register<IUserService3, UserService3>();
            builder.Register<IAnotherService, AnotherService>();
    // And many more Services...

    _container = builder.Build();

    var userService1 = _container.Resolve<IUserService1>();
    var userService2 = _container.Resolve<IUserService2>();
    var userService3 = _container.Resolve<IUserService3>();
var anotherService = _container.Resolve<IAnotherService>();     

    var vm = new MainController(userService1,userService2,userService3,anotherService)
}

public class MainController
{    
    private UserController1 _userVM1;
    private UserController2 _userVM2;
    private UserController3 _userVM3;

    public MainController(IUserService1 userService1,IUserService2 userService2,IUserService3 userService3,anotherService)
    {    
        _userVM1 = new UserController1(userService1,anotherService);
        _userVM2 = new UserController2(userService2,...,...);
        _userVM3 = new UserController3(userService3,...,...,...);     
    }
} 

// Such a Controller class needs to be created 10 times... and what I do here is typical for all Controllers driving the GUI
public class UserController1
{
    private readonly IUserService1 _userService1; 

    public UserController1(IUserService1 userService1,IAnotherService anotherService)
    {
        _userService1 = userService1;           
        //Bind data to GUI
        UserData1Collection = ConvertModelIntoViewModelCollection(userService1,anotherService);
    }

    public ObservableCollection<UserData1> UserData1Collection { get; set; }

    private ObservableCollection<UserData1ViewModel> ConvertModelIntoViewModelCollection(IAnotherService anotherService)
    {      
        var userData1ViewModelCollection = new ObservableCollection<UserData1ViewModel>();
        _userService1.GetUserData1().ForEach(user =>
        {
            userData1ViewModelCollection.Add(new UserData1ViewModel(user, anotherService,...));
        });           
        return userData1ViewModelCollection; 
    }
}

Теперь вопрос:

Существует много провалов / пропусков через службы, потому что мне приходится вызывать службы, когда, например, свойства viewmodels меняются через lost_focus на элементах управления графического интерфейса.

Это нормально, что я делаю?Видите ли вы какие-либо недостатки?Или как бы вы это сделали?

Обновление

Этот DI - это массовая атака на мои порочные привычки: P

  1. Вы имели в виду, что так может?

  2. Кстати.почему я должен делать эту фабрику контроллеров?Почему тогда не ServiceFactory тоже ... тогда мы вернулись к ServiceLocator ...

  3. Как мне теперь получить экземпляры контроллера в моей MainViewModel?через расширение конструктора моего MVM многими дополнительными параметрами?в итоге 30 параметров?...

protected override void OnStartup(StartupEventArgs e)
{
    IContainerBuilder builder = new ContainerBuilder();

    // Firstly Register ALL existing Services            
    builder.Register<IAdminService, AdminService>();
    builder.Register<IDocumentService, DocumentService>();
    builder.Register<ILessonPlannerService, LessonPlannerService>();
    builder.Register<IMediator, Mediator>();
    builder.Register<IMainRepository, MainRepository>();           
    builder.Register<MainViewModel>();

    IContainer _container = builder.Build();

    // THEN Register ALL Controllers needing the previously registered Services
    IControllerFactory factory = new ControllerFactory(builder);
    IDailyPlanner controller1 = factory.Create<IDailyPlanner>();
    IWeeklyPlanner controller2 = factory.Create<IWeeklyPlanner>();
    SchoolclassAdministrationViewModel controller3 = factory.Create<SchoolclassAdministrationViewModel>();

    // THEN Register the mainViewModel(MainController) which should take ALL Services and ALL Controller... WOW thats a massive Ctor param count... is that pure? Did you mean it that way???
    MainViewModel mainViewModel = _container.Resolve<MainViewModel>();

    //MainWindow mainWindow = _container.Resolve<MainWindow>();
    //mainWindow.DataContext = mainViewModel;   
    //mainWindow.ShowDialog();   
} 

public class ControllerFactory : IControllerFactory
{
    private readonly IContainerBuilder _builder;
    private readonly IContainer _container;

    /// <summary>
    /// Takes the IOC container to register all Controllers
    /// </summary>
    public ControllerFactory(IContainerBuilder builder)
    {
        _builder = builder;

        _builder.Register<SchoolclassAdministrationViewModel>();
        _builder.Register<IDailyPlanner, LessonPlannerDailyViewModel>();
        _builder.Register<IWeeklyPlanner, LessonPlannerWeeklyViewModel>();
        _container = _builder.Build();
    }

    /// <summary>
    /// Returns an Instance of a given Type
    /// </summary>
    public T Create<T>()
    {
        return _container.Resolve<T>();
    }
}

Update2 :

Теперь я изменил свой код, так что MainViewModel принимает IControllerFactory какПараметр и добавил эти две строки кода в класс App:

builder.Register<IControllerFactory, ControllerFactory>();
builder.Register<IContainerBuilder, ContainerBuilder>(); 

Таким образом, мне не нужно передавать все контроллеры в MainViewModel Ctor, а MainViewModel получает экземпляры контроллеров с фабрики.

Есть ли что-нибудь лучшее, что я могу сделать здесь?Или это приемлемое хорошее решение?У меня нет опыта работы с DI, поэтому я спрашиваю:)

Update3

ОК. Я сделал некоторый рефакторинг кода и сделал комментарии для других, чтобы они поняли, какое окончательное решение:

protected override void OnStartup(StartupEventArgs e)
{
    IContainerBuilder builder = new ContainerBuilder();

    // Firstly Register ALL existing Services          
    builder.Register<IAdminService, AdminService>();
    builder.Register<IDocumentService, DocumentService>();
    builder.Register<ILessonPlannerService, LessonPlannerService>();
    builder.Register<IMediator, Mediator>();
    builder.Register<IMainRepository, MainRepository>();
    builder.Register<IControllerFactory, ControllerFactory>();              
    builder.Register<IDailyPlanner, LessonPlannerDailyViewModel>();
    builder.Register<IWeeklyPlanner, LessonPlannerWeeklyViewModel>();

    // Just for visual separation THEN register the MainController driving all other Controllers created via the IControllerFactory          
    builder.Register<MainViewModel>();

    // Build the container
    IContainer container = builder.Build();

    // THEN Register the MainController which should take ALL IServices and the IFactory
    MainViewModel mainViewModel = container.Resolve<MainViewModel>();

    // LATER in the mainViewModel`s Ctor you can create all 10 Controller instances with the IControllerFactory like this
    // _dailyPlannerController = controllerFactory.Create<IDailyPlanner>();

    MainWindow mainWindow = new MainWindow();
    mainWindow.DataContext = mainViewModel;   
    mainWindow.ShowDialog();   
}

public class ControllerFactory : IControllerFactory
{
    private readonly IContainer _container;

    /// <summary>
    /// Takes the IOC container to resolve all Controllers
    /// </summary>
    public ControllerFactory(IContainer container)
    {
        _container = container; 
    }

    /// <summary>
    /// Returns an Instance of a given Type
    /// </summary>
    public T Create<T>()
    {
        return _container.Resolve<T>();
    }
}

Большое спасибо за ваше время, @Can.Я многому научился!

1 Ответ

9 голосов
/ 25 июня 2011

Мне кажется, вы неправильно поняли, как использовать контейнер IoC.Вместо того чтобы создавать экземпляры ваших служб и передавать их в качестве параметров, вам нужно попросить контейнер разрешить их для вас.

Например, вы можете выполнить рефакторинг своего кода следующим образом, чтобы правильно использовать IoC:

protected void Application_Start(object sender, EventArgs e)
{
    var builder = new ContainerBuilder();

    builder.Register<IUserService1, UserService1>();
    builder.Register<IUserService2, UserService2>();
    builder.Register<IUserService3, UserService3>();
    builder.Register<IAnotherService, AnotherService>();

    builder.Register<MainController, MainController>();
    // And many more Services...

    _container = builder.Build();

    //let the container inject all the required dependencies into MainController..
    var vm = _container.Resolve<MainController>();
}

Контейнер в этом случае должен управлять жизненным циклом вашего объекта MainController и обеспечивать, чтобы все зависимости (свойства и параметры конструктора, которые необходимо инициализировать) были внедрены и заполнены.

Чтослучится так, что контейнер поймет, что для создания экземпляра MainController ему понадобятся IUserService1, IUserService2 и т. д., и, в свою очередь, посмотрит, сможет ли он создать какие-либо экземпляры этих объектов, просмотрев другие типы, зарегистрированные в контейнере.Это будет сделано рекурсивным способом для построения дерева зависимостей, пока все зависимости класса не будут удовлетворены.Полученный MainController, который вы получите, уже будет содержать все зависимости, вставленные в него.

В идеале вы должны вызывать Resolve () как можно меньше мест, чтобы структурировать ваше приложение таким образом, чтобы был только один корень,Для более глубокого изучения внедрения зависимостей я настоятельно рекомендую книгу Марка Симана Внедрение зависимостей в .NET , которая, на мой взгляд, является одним из лучших введений в DI.

ОБНОВЛЕНИЕ:

Причина, по которой я предложил использовать ControllerFactory, заключалась в том, что у вас было много классов UserController в вашем MainController, и, передавая их как зависимость, вы в итоге получили бы более 10 параметров конструктора, а неупомянуть, что нужно добавить больше при создании новых контроллеров.Если ваши view-модели имеют зависимость только от одного контроллера, то не имеет смысла использовать фабрику таким образом, и вы можете иметь прямую зависимость от требуемого контроллера.

Что касается ServiceFactory, он не нужен, поскольку для каждого из ваших классов вряд ли потребуются ВСЕ доступные классы обслуживания, только некоторые из них.В этом случае лучше указывать их явно для каждой службы в конструкторе.

Вы также должны зарегистрировать все свои экземпляры в одном месте (или в небольших классах установщика), а не в конструкторе для разных классов.

Вот вопрос, который более специфичен для MVVM и должен помочь вам понять, как структурировать ваши классы и зависимости: Как я могу объединить MVVM и внедрение зависимостей в приложении WPF?

...