代码之家  ›  专栏  ›  技术社区  ›  ricky

如何将依赖注入MVVM视图模型类?

  •  7
  • ricky  · 技术社区  · 7 年前

    我熟悉WPF和MVVM模式。现在,我正在用Autofac在我的新WPF应用程序中练习依赖项注入模式。我想弄清楚如何将依赖注入MVVM视图模型类。

    正如下面所附的代码,我有一个根视图模型类 MainViewModel ,它可以创建其他视图模型(例如。 MonitorPageViewModel )需要时的实例。在里面 监视器页面视图模型 ,它还需要创建其他子视图模型(例如。 MonitorDashboardViewModel )需要时的实例。所有这些视图模型可能都有几个依赖项( ILogger ,则, IRepository<RawMessage> ,则, IParsingService ,等等)。

    在我以前没有使用任何IoC容器的WPF项目中,我总是 new 需要时在父视图模型中创建子视图模型,并使用ServiceLocator提供所需的服务。现在,我想弄清楚 一种更具依赖性的注入方式 这样做。

    我有几种方法(在下面的代码中演示),但我对其中任何一种都不满意。

    1. 使用IoC容器获取要创建的响应 主视图模型 ;并将IoC容器实例注入 主视图模型 ;然后,使用IoC容器实例来解析子视图模型构造函数所需的每个对象。如果子视图模型类需要解析其他类,则将IoC注入其中。[ 听起来是另一个ServiceLocator ]。
    2. 注入所需的所有服务 主视图模型 及其后代视图模型,并沿视图模型链传递服务。和 所需的视图模型实例。[ 需要注入大量服务,并将其传递下去 ]

    我不想使用MVVM框架,例如 Caliburn.Micro 在本项目中。有没有简单而优雅的解决方案?

    public interface ILogger
    {
        // ...
    }
    
    public interface IRepository<T>
    {
        // ...
    }
    
    public interface IStatisticsService
    {
        // ...
    }
    
    public class RawMessage
    {
        // ...
    }
    
    public class Device
    {
        // ...
    }
    
    public class Parser
    {
        // ...
    }
    
    
    public interface IParsingService
    {
        void Parse(Parser parser);
    }
    
    public class DockPaneViewModel : ViewModelBase
    {
        // ...
    }
    
    public class HomePageViewModel : DockPaneViewModel
    {
        public HomePageViewModel(ILogger logger)
        {
            // ...
        }
    }
    
    public class MonitorDashboardViewModel : DockPaneViewModel
    {
        public MonitorDashboardViewModel(IStatisticsService statisticsService)
        {
            // ...
        }
    }
    
    public class MonitorPageViewModel : DockPaneViewModel
    {
        public MonitorPageViewModel(ILogger logger, IRepository<RawMessage> repository,
            IRepository<Parser> parserRepository, IParsingService parsingService)
        {
            // ...
        }
    
        public void CreateDashboard()
        {
            IStatisticsService statisticsService = ??; // how to resolve the service?
            var dashBoardVm = new MonitorDashboardViewModel(statisticsService); // how to create this? 
        }
    }
    
    public class ResourceManagementViewModel : DockPaneViewModel
    {
        public ResourceManagementViewModel(ILogger logger, IRepository<Device> deviceRepository)
        {
            // ...
        }
    }
    

    这是 主视图模型 有替代施工人员

    public class MainViewModel : ViewModelBase
    {
        public ObservableCollection<DockPaneViewModel> DockPanes
        {
            get;
            set;
        } = new ObservableCollection<DockPaneViewModel>();
    
        #region approach 1
        // use the IOC container take the response to create MainViewModel;
        // and inject the Ioc Container instance to MainViewModel;
        // then, use the IOC container instance to resovle every thing the child view models need
        private readonly ISomeIocContainer _ioc;
        public MainViewModel(ISomeIocContainer ioc)
        {
            _ioc = ioc;
        }
    
        public void ResetPanes_1()
        {
            DockPanes.Clear();
            DockPanes.Add(new HomePageViewModel(_ioc.Resolve<ILogger>())); // how to new child view model and how to provide the constructor parameters?
            DockPanes.Add(new MonitorPageViewModel(_ioc.Resolve<ILogger>(),
                _ioc.Resolve< IRepository<RawMessage>>(), 
                _ioc.Resolve<IRepository<Parser>>(),
                _ioc.Resolve<IParsingService>())); // also need to inject ISomeIocContainer to MonitorDashboardViewModel for resolve IStatisticsService
            DockPanes.Add(new ResourceManagementViewModel(_ioc.Resolve<ILogger>(), 
                _ioc.Resolve<IRepository<Device>>()));
            // add other panes
        }
        #endregion
    
        #region approach 2
        // pasing all dependencies of MainViewModel and all descendant View Models in to MainViewModel,
        // and pass dependencies along the ViewModel chain.
        private readonly ILogger _logger;
        private readonly IRepository<RawMessage> _repository;
        private readonly IRepository<Parser> _parserRepository;
        private readonly IRepository<Device> _deviceRepository;
        private readonly IParsingService _parsingService;
        private readonly IStatisticsService _statisticsService;
        public MainViewModel(ILogger logger, IRepository<RawMessage> repository,
            IRepository<Parser> parserRepository, IRepository<Device> deviceRepository,
            IParsingService parsingService, IStatisticsService statisticsService)
        {
            _logger = logger;
            _repository = repository;
            _parserRepository = parserRepository;
             _deviceRepository = deviceRepository;
            _parsingService = parsingService;
            _statisticsService = statisticsService;
        }
    
        public void ResetPanes_2()
        {
            DockPanes.Clear();
            DockPanes.Add(new HomePageViewModel(_logger)); // how to new child view model and how to provide the constructor parameters?
            DockPanes.Add(new MonitorPageViewModel(_logger, _repository, _parserRepository, _parsingService)); // also need pass statisticsService down 
            DockPanes.Add(new ResourceManagementViewModel(_logger, _deviceRepository));
            // add other panes
        }
        #endregion
    }
    
    1 回复  |  直到 7 年前
        1
  •  8
  •   Nkosi    4 年前

    有些时候,回归基本原则并保持简单(亲吻)往往会奏效。

    在这种情况下,我想到的是 The Explicit Dependency Principle Pure Dependency Injection

    这个 MainViewModel 通过注入容器(big no no)或具有多个依赖项(代码气味),显然做得太多了。试着缩小班级应该做什么(SRP)

    因此,假设主视图模型需要一组窗格。那为什么不满足它的需要呢。

    public class MainViewModel : ViewModelBase {
        public ObservableCollection<DockPaneViewModel> DockPanes { get; set; }
    
        //Give the view model only what it needs
        public MainViewModel(IEnumerable<DockPaneViewModel> panes) {
            DockPanes = new ObservableCollection<DockPaneViewModel>(panes);
        }
    
        public void ResetPanes() {
            foreach (var pane in DockPanes) {
                pane.Reset();
            }
            //notify view
        }
    }
    

    注意基板的细微变化

    public abstract class DockPaneViewModel : ViewModelBase {
        // ...
    
        public virtual void Reset() {
            //...
        }
    }
    

    主视图模型不应该关心如何创建依赖关系。它只关心得到它明确要求的东西。

    这同样适用于不同的窗格实现。

    如果视图模型需要能够创建多个子对象,则将该职责委托给工厂。

    public class MonitorPageViewModel : DockPaneViewModel {
        public MonitorPageViewModel(ILogger logger, IRepository<RawMessage> repository,
            IRepository<Parser> parserRepository, IParsingService parsingService, 
            IPaneFactory factory) {
            // ...
        }
    
        public void CreateDashboard() {
            var dashBoardVm = factory.Create<MonitorDashboardViewModel>();
            
            //...
        }
    }
    

    同样,主体应尽可能少地承担责任。

    View First或ViewModel First被视为实现问题,如果遵循配置模型的约定,则实际上并不重要。

    如果设计做得很好,那么无论您使用的是框架还是纯代码都无关紧要。

    然而,当涉及到将所有内容组合在一起时,这些框架确实派上了用场。最简单、最优雅的解决方案是让一些东西创建对象图,但如果没有这些东西,您只能自己在合成根中构建它。