# 依赖注入(DI)迁移指南 ## 概述 YZWater3 已引入 `Microsoft.Extensions.DependencyInjection` 框架,采用**渐进式迁移**策略,从静态服务逐步过渡到依赖注入。 ## 当前状态 ### 已完成接口化 - ✅ `INotificationService` - 通知服务(企业微信/钉钉/邮件) ### 待迁移(保持静态访问) - `PlcService` - PLC 通信 - `AlarmService` - 报警管理 - `DatabaseService` - 数据库 - `AuthService` - 认证授权 - `ConfigService` - 配置管理 - `HubServer` / `HubClient` - WebSocket 通信 ## 使用方式 ### 方式一:Service Locator(推荐过渡期使用) ```csharp // 获取服务 var notificationService = ServiceLocator.GetService(); // 发送通知 await notificationService.SendAlarmNotificationAsync(alarm); ``` ### 方式二:构造函数注入(最终目标) ```csharp public class MyViewModel : ObservableObject { private readonly INotificationService _notificationService; public MyViewModel(INotificationService notificationService) { _notificationService = notificationService; } public async Task SomeMethod() { await _notificationService.SendAlarmNotificationAsync(alarm); } } ``` ### 方式三:静态访问(向后兼容) ```csharp // 现有代码无需修改,继续使用静态类 NotificationService.Instance.SendAlarmNotificationAsync(alarm); ``` ## 迁移步骤 ### 第一步:配置 DI 容器 在 `App.axaml.cs` 的 `OnStartup` 中: ```csharp using Microsoft.Extensions.DependencyInjection; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 配置 DI var services = new ServiceCollection(); services.AddYZWaterCore(); var serviceProvider = services.BuildServiceProvider(); ServiceLocator.Configure(serviceProvider); // 原有初始化代码... } ``` ### 第二步:逐步迁移服务 以 `PlcService` 为例: 1. **创建实例类实现接口** ```csharp // 新建 PlcServiceImpl.cs public class PlcServiceImpl : IPlcService { // 实现接口方法... } ``` 2. **静态类作为门面(保持兼容)** ```csharp // PlcService.cs 保持静态类 public static class PlcService { private static IPlcService? _impl; public static void Initialize(IPlcService impl) { _impl = impl; } public static bool IsConnected => _impl?.IsConnected ?? false; public static Task ConnectAsync() => _impl?.ConnectAsync() ?? Task.FromResult(false); // 其他方法委托给 _impl... } ``` 3. **在 DI 配置中注册** ```csharp services.AddSingleton(); ``` 4. **初始化静态门面** ```csharp var plcService = serviceProvider.GetRequiredService(); PlcService.Initialize(plcService); ``` ### 第三步:逐步替换调用方 ```csharp // 旧代码 PlcService.ConnectAsync(); // 新代码(ViewModel 中) public class MyViewModel { private readonly IPlcService _plcService; public MyViewModel(IPlcService plcService) { _plcService = plcService; } public async Task Connect() { await _plcService.ConnectAsync(); } } ``` ## 接口定义 ### IPlcService ```csharp public interface IPlcService { bool IsConnected { get; } Task ConnectAsync(); void Disconnect(); Task ReadBytesAsync(string address, ushort length); Task ReadFloatAsync(string address); Task ReadBoolAsync(string address); Task WriteAsync(string address, byte[] value); float ParseFloat(byte[] data, int offset); bool ParseBit(byte[] data, int byteOffset, int bitIndex); } ``` ### IAlarmService ```csharp public interface IAlarmService { bool HasActiveAlarm { get; } int ActiveAlarmCount { get; } void Start(); void Stop(); Task AcknowledgeAlarmAsync(int alarmId, string operatorName = "Operator"); Task AcknowledgeAllAsync(string operatorName = "Operator"); Task> GetUnacknowledgedAlarmsAsync(); Task> GetAlarmHistoryAsync(DateTime start, DateTime end); event Action? AlarmRaised; event Action? AlarmAcknowledged; event Action? AlarmStateChanged; } ``` ### INotificationService ```csharp public interface INotificationService { bool IsEnabled { get; set; } void AddChannel(INotificationChannel channel); void RemoveChannel(string channelName); Task SendNotificationAsync(string key, string title, string message, NotificationLevel level); Task SendAlarmNotificationAsync(AlarmRecord alarm); Task SendEscalationNotificationAsync(AlarmRecord alarm); Task SendPlcConnectionNotificationAsync(bool isConnected); } ``` ## 测试示例 使用 DI 后,可以方便地进行单元测试: ```csharp [Fact] public async Task AlarmService_ShouldRaiseNotification_WhenAlarmTriggered() { // Arrange var mockNotification = new Mock(); var alarmService = new AlarmService(mockNotification.Object); // Act alarmService.TriggerAlarm("Test", "Test alarm", 2); // Assert mockNotification.Verify(x => x.SendAlarmNotificationAsync(It.IsAny()), Times.Once); } ``` ## 迁移优先级 1. **高优先级**(影响可测试性) - `PlcService` - 核心业务逻辑 - `AlarmService` - 报警判断 - `AuthService` - 权限检查 2. **中优先级**(影响模块化) - `DatabaseService` - 数据访问 - `ConfigService` - 配置管理 3. **低优先级**(相对独立) - `HubServer` / `HubClient` - 通信层 - `AuditService` - 审计日志 ## 注意事项 1. **向后兼容**:静态门面确保现有代码无需立即修改 2. **渐进迁移**:可以逐个服务、逐个调用方迁移 3. **测试覆盖**:迁移时补充单元测试 4. **线程安全**:确保服务实例是线程安全的 5. **生命周期**:注意服务的生命周期管理(单例/瞬态/作用域) ## 参考资料 - [Microsoft.Extensions.DependencyInjection 文档](https://docs.microsoft.com/aspnet/core/fundamentals/dependency-injection) - [Service Locator 模式](https://martinfowler.com/articles/injection.html#UsingAServiceLocator) - [依赖注入原则](https://docs.microsoft.com/dotnet/architecture/modern-web-apps-azure/dependency-injection)