WPF Prism (二):依赖注入与模块化设计

张开发
2026/4/18 1:07:43 15 分钟阅读

分享文章

WPF Prism (二):依赖注入与模块化设计
1. 依赖注入Prism框架的万能胶水第一次接触Prism框架时最让我眼前一亮的不是MVVM而是它优雅的依赖注入(DI)设计。想象你正在组装一台电脑CPU、内存、硬盘这些组件不需要自己焊接只需要按照接口规格插到主板上就行——这就是依赖注入的直观体现。在Prism中Unity或DryIoc这样的IOC容器就是那块主板而各个模块就是即插即用的硬件组件。实际项目中我遇到过这样的场景一个订单模块需要调用用户服务传统做法可能直接在订单类里new一个用户服务实例。但使用Prism后只需要在构造函数声明IUserService参数容器会自动帮你装配好实例。这种松耦合设计让单元测试变得异常简单——你可以轻松注入Mock对象代替真实服务。// 传统紧耦合写法 public class OrderService { private UserService _userService new UserService(); public void ProcessOrder() { var user _userService.GetCurrentUser(); // ... } } // 使用依赖注入的写法 public class OrderService { private readonly IUserService _userService; public OrderService(IUserService userService) { _userService userService; } public void ProcessOrder() { var user _userService.GetCurrentUser(); // ... } }2. 模块化设计像搭积木一样开发WPF应用2.1 模块化架构的优势去年接手一个ERP系统改造项目时我深刻体会到模块化的价值。原有系统所有功能都挤在一个200MB的Assembly里每次修改一个小功能就要重新部署整个应用。采用Prism模块化设计后我们将系统拆分为核心模块包含基础服务订单模块库存模块报表模块每个模块可以独立开发、测试甚至热更新。当客户提出只要先上线采购功能的需求时我们只需要启用对应模块即可这种灵活性让项目交付周期缩短了40%。2.2 模块化实战步骤创建模块类库项目安装Prism.Modularity NuGet包实现IModule接口public class InventoryModule : IModule { private readonly IRegionManager _regionManager; private readonly IContainerProvider _container; public InventoryModule(IRegionManager regionManager, IContainerProvider container) { _regionManager regionManager; _container container; } public void OnInitialized(IContainerProvider containerProvider) { // 注册模块视图到区域 _regionManager.RegisterViewWithRegion(MainRegion, () _container.ResolveInventoryView()); } public void RegisterTypes(IContainerRegistry containerRegistry) { // 注册模块内服务 containerRegistry.RegisterSingletonIInventoryService, InventoryService(); } }在App.xaml.cs中加载模块protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModuleInventoryModule(); }3. Prism容器选型指南3.1 Unity vs DryIoc在最近三个项目中我分别使用了Unity和DryIoc两种容器。这里分享实测对比特性UnityDryIoc性能中等优秀注册语法显式注册为主支持自动注册生命周期管理完善更精细AOP支持内置需扩展学习曲线平缓较陡峭个人建议新项目优先考虑DryIoc特别是需要高性能的场景维护旧项目继续用Unity更稳妥。3.2 容器配置技巧在电商项目里我总结出几个实用技巧分层注册将服务按层级注册便于替换实现containerRegistry.RegisterIDataService, SqlDataService(SQL); containerRegistry.RegisterIDataService, MongoDataService(NoSQL);条件解析根据不同环境加载不同服务var service container.ResolveIDataService( Environment.IsDevelopment() ? SQL : NoSQL);属性注入对第三方组件特别有用[Dependency] public ILogger Logger { get; set; }4. 典型问题解决方案4.1 循环依赖陷阱在开发消息通知系统时我踩过循环依赖的坑NotificationService依赖UserService而UserService又需要NotificationService。Prism提供了两种解决方案属性注入public class UserService { [Dependency] public INotificationService NotificationService { get; set; } }接口分离public interface IUserBasicService {} public interface IUserNotificationService {} public class UserService : IUserBasicService, IUserNotificationService { public UserService(INotificationService notificationService) { // ... } }4.2 模块通信最佳实践当订单模块需要通知库存模块更新时我推荐使用Prism的事件聚合器// 定义事件 public class InventoryUpdateEvent : PubSubEventint {} // 发布事件 eventAggregator.GetEventInventoryUpdateEvent().Publish(productId); // 订阅事件 eventAggregator.GetEventInventoryUpdateEvent() .Subscribe(id UpdateStock(id));记得在模块卸载时取消订阅public void Unload() { eventAggregator.GetEventInventoryUpdateEvent() .Unsubscribe(_token); }5. 性能优化实战5.1 延迟加载模块对于大型应用可以采用按需加载模块的方式提升启动速度protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { var reportModule new ModuleInfo { ModuleName typeof(ReportModule).Name, ModuleType typeof(ReportModule).AssemblyQualifiedName, InitializationMode InitializationMode.OnDemand }; moduleCatalog.AddModule(reportModule); } // 需要时加载 moduleManager.LoadModule(ReportModule);5.2 服务生命周期管理错误的生命周期配置会导致内存泄漏。我的经验法则是Singleton全局唯一服务如配置服务Scoped请求级服务在WPF中较少使用Transient每次获取新实例轻量级服务// 正确示例 containerRegistry.RegisterSingletonIConfigService, ConfigService(); containerRegistry.RegisterIDataProcessor, DataProcessor();6. 测试驱动开发6.1 单元测试技巧使用Prism的DI容器可以轻松实现测试替身[Test] public void OrderProcess_Should_CallInventory() { // 准备 var container new ContainerRegistry(); var mockInventory new MockIInventoryService(); container.RegisterInstance(mockInventory.Object); // 执行 var orderService container.ResolveOrderService(); orderService.ProcessOrder(); // 断言 mockInventory.Verify(x x.UpdateStock(It.IsAnyint()), Times.Once); }6.2 UI测试方案对于模块化UI我推荐组合使用以下工具Prism的Region导航测试[Test] public void Navigation_Should_LoadInventoryView() { var region new Region(); region.Add(view); region.RequestNavigate(InventoryView); Assert.IsTrue(region.ActiveViews.Contains(view)); }自动化UI测试框架如AppiumWinAppDriver7. 项目结构建议经过多个项目实践我总结出这样的目录结构最合理MyApp/ ├── Core/ # 核心模块 │ ├── Services/ # 基础服务 │ └── Models/ # 核心模型 ├── Modules/ │ ├── Order/ # 订单模块 │ │ ├── Views/ # 模块视图 │ │ ├── ViewModels/ │ │ └── Services/ # 模块专属服务 │ └── Inventory/ # 库存模块 └── Infrastructure/ # 基础设施 ├── Logging/ # 日志组件 └── DAL/ # 数据访问层关键点每个模块都是独立类库模块之间通过接口通信核心模块不依赖具体业务模块8. 调试与排错8.1 常见错误排查类型未注册检查RegisterTypes方法是否遗漏注册循环依赖使用属性注入或重构设计区域未定义确保xaml中正确设置了prism:RegionManager.RegionName8.2 日志记录技巧在容器初始化时添加日志protected override IContainerExtension CreateContainerExtension() { var container new DryIocContainerExtension(); container.Instance.SetLogger(new DebugLogger()); return container; }这样所有容器操作都会输出到Debug窗口便于跟踪依赖解析过程。

更多文章