DI_MIGRATION.md 6.2 KB

依赖注入(DI)迁移指南

概述

YZWater3 已引入 Microsoft.Extensions.DependencyInjection 框架,采用渐进式迁移策略,从静态服务逐步过渡到依赖注入。

当前状态

已完成接口化

  • ✅ INotificationService - 通知服务(企业微信/钉钉/邮件)

待迁移(保持静态访问)

  • PlcService - PLC 通信
  • AlarmService - 报警管理
  • DatabaseService - 数据库
  • AuthService - 认证授权
  • ConfigService - 配置管理
  • HubServer / HubClient - WebSocket 通信

使用方式

方式一:Service Locator(推荐过渡期使用)

// 获取服务
var notificationService = ServiceLocator.GetService<INotificationService>();

// 发送通知
await notificationService.SendAlarmNotificationAsync(alarm);

方式二:构造函数注入(最终目标)

public class MyViewModel : ObservableObject
{
    private readonly INotificationService _notificationService;

    public MyViewModel(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public async Task SomeMethod()
    {
        await _notificationService.SendAlarmNotificationAsync(alarm);
    }
}

方式三:静态访问(向后兼容)

// 现有代码无需修改,继续使用静态类
NotificationService.Instance.SendAlarmNotificationAsync(alarm);

迁移步骤

第一步:配置 DI 容器

在 App.axaml.cs 的 OnStartup 中:

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. 创建实例类实现接口

    // 新建 PlcServiceImpl.cs
    public class PlcServiceImpl : IPlcService
    {
    // 实现接口方法...
    }
    
  2. 静态类作为门面(保持兼容)

    // 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<bool> ConnectAsync()
        => _impl?.ConnectAsync() ?? Task.FromResult(false);
    
    // 其他方法委托给 _impl...
    }
    
  3. 在 DI 配置中注册

    services.AddSingleton<IPlcService, PlcServiceImpl>();
    
  4. 初始化静态门面

    var plcService = serviceProvider.GetRequiredService<IPlcService>();
    PlcService.Initialize(plcService);
    

第三步:逐步替换调用方

// 旧代码
PlcService.ConnectAsync();

// 新代码(ViewModel 中)
public class MyViewModel
{
    private readonly IPlcService _plcService;

    public MyViewModel(IPlcService plcService)
    {
        _plcService = plcService;
    }

    public async Task Connect()
    {
        await _plcService.ConnectAsync();
    }
}

接口定义

IPlcService

public interface IPlcService
{
    bool IsConnected { get; }
    Task<bool> ConnectAsync();
    void Disconnect();
    Task<byte[]?> ReadBytesAsync(string address, ushort length);
    Task<float> ReadFloatAsync(string address);
    Task<bool> ReadBoolAsync(string address);
    Task<bool> WriteAsync(string address, byte[] value);
    float ParseFloat(byte[] data, int offset);
    bool ParseBit(byte[] data, int byteOffset, int bitIndex);
}

IAlarmService

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<List<AlarmRecord>> GetUnacknowledgedAlarmsAsync();
    Task<List<AlarmRecord>> GetAlarmHistoryAsync(DateTime start, DateTime end);

    event Action<AlarmRecord>? AlarmRaised;
    event Action<AlarmRecord>? AlarmAcknowledged;
    event Action? AlarmStateChanged;
}

INotificationService

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 后,可以方便地进行单元测试:

[Fact]
public async Task AlarmService_ShouldRaiseNotification_WhenAlarmTriggered()
{
    // Arrange
    var mockNotification = new Mock<INotificationService>();
    var alarmService = new AlarmService(mockNotification.Object);

    // Act
    alarmService.TriggerAlarm("Test", "Test alarm", 2);

    // Assert
    mockNotification.Verify(x => x.SendAlarmNotificationAsync(It.IsAny<AlarmRecord>()), Times.Once);
}

迁移优先级

  1. 高优先级(影响可测试性)

    • PlcService - 核心业务逻辑
    • AlarmService - 报警判断
    • AuthService - 权限检查
  2. 中优先级(影响模块化)

    • DatabaseService - 数据访问
    • ConfigService - 配置管理
  3. 低优先级(相对独立)

    • HubServer / HubClient - 通信层
    • AuditService - 审计日志

注意事项

  1. 向后兼容:静态门面确保现有代码无需立即修改
  2. 渐进迁移:可以逐个服务、逐个调用方迁移
  3. 测试覆盖:迁移时补充单元测试
  4. 线程安全:确保服务实例是线程安全的
  5. 生命周期:注意服务的生命周期管理(单例/瞬态/作用域)

参考资料