1. 工位检测软件中的Form设计痛点
在双工位检测系统的开发过程中,我们最初采用了典型的Form集中式设计。所有工位流程逻辑都直接写在Form的代码文件中,这种设计在初期确实快速实现了功能,但随着业务复杂度提升,问题逐渐暴露:
- 代码臃肿:主Form文件膨胀到3000+行代码,包含按钮事件、数据校验、流程控制等混杂逻辑
- 维护困难:修改某个工位流程时需要反复上下滚动查找相关代码段
- 复用性差:相同检测逻辑在不同工位间无法直接复用,存在大量重复代码
- 测试不便:无法对单个工位流程进行独立单元测试
典型的反例代码结构如下:
// MainForm.cs private void btnStation1Start_Click(object sender, EventArgs e) { // 工位1的启动逻辑(约200行) } private void btnStation2Check_Click(object sender, EventArgs e) { // 工位2的检测逻辑(约150行) } // 其他十几个工位相关方法...2. 流程拆解的核心方案设计
2.1 采用Partial Class分离关注点
通过partial class将不同工位的流程代码拆分到独立文件,保持原有Form类的完整性:
解决方案结构: - MainForm.cs(基础UI和公共方法) - MainForm.Station1.cs(工位1专属逻辑) - MainForm.Station2.cs(工位2专属逻辑) - MainForm.CommProtocol.cs(通信协议处理)关键实现步骤:
- 将原Form类声明改为partial:
public partial class MainForm : Form- 创建工位流程专属文件(如MainForm.Station1.cs):
partial class MainForm { // 工位1的私有字段 private DateTime _station1StartTime; // 工位1的初始化方法 private void InitStation1() { // 控件事件绑定 btnStation1Start.Click += Station1StartHandler; } // 工位1的启动逻辑 private void Station1StartHandler(object sender, EventArgs e) { // 优化后的业务逻辑 } }2.2 建立StationFlowController基类
为统一各工位流程的接口规范,设计抽象基类:
public abstract class StationFlowController { // 工位状态枚举 public enum StationStatus { Idle, Testing, Completed } // 必须实现的抽象方法 public abstract void Initialize(); public abstract bool StartTest(); public abstract void EmergencyStop(); // 公共属性 public string StationName { get; protected set; } public StationStatus CurrentStatus { get; protected set; } }具体工位控制器实现示例:
public class Station1Controller : StationFlowController { public override void Initialize() { // 硬件初始化 ComPortManager.Open("COM3"); // UI初始化 MainForm.Instance.SetStation1LED(Color.Yellow); } public override bool StartTest() { // 具体的检测流程 } }3. 重构过程中的关键技术点
3.1 事件总线的引入
解决跨工位通信问题,避免直接Form引用:
// 定义事件类型 public class TestCompletedEvent { public int StationId { get; set; } public bool Result { get; set; } } // 事件总线服务 public static class EventBus { private static readonly Dictionary<Type, List<Delegate>> _handlers = new(); public static void Subscribe<T>(Action<T> handler) { var type = typeof(T); if (!_handlers.ContainsKey(type)) { _handlers[type] = new List<Delegate>(); } _handlers[type].Add(handler); } public static void Publish<T>(T @event) { if (_handlers.TryGetValue(typeof(T), out var handlers)) { foreach (var handler in handlers) { ((Action<T>)handler)(@event); } } } } // 工位控制器中的使用示例 EventBus.Subscribe<TestCompletedEvent>(e => { if (e.StationId == 1) { // 处理工位1完成事件 } });3.2 依赖注入优化
在Program.cs中配置服务容器:
var services = new ServiceCollection(); services.AddSingleton<MainForm>(); services.AddTransient<IStation1Service, Station1Service>(); services.AddTransient<IStation2Service, Station2Service>(); // 构建服务提供者 var provider = services.BuildServiceProvider(); // 启动主窗体 Application.Run(provider.GetRequiredService<MainForm>());4. 实际改造中的经验教训
4.1 控件访问的线程安全
原Form中直接操作UI控件会导致跨线程异常,改造方案:
// 在MainForm中定义线程安全方法 public void SetStationStatusLED(int stationId, Color color) { if (InvokeRequired) { Invoke(new Action(() => SetStationStatusLED(stationId, color))); return; } // 实际UI更新逻辑 switch(stationId) { case 1: ledStation1.BackColor = color; break; case 2: ledStation2.BackColor = color; break; } }4.2 配置文件迁移策略
原Form中的硬编码参数需要提取到独立配置类:
// StationConfig.cs public class StationConfig { [JsonProperty("station1_com_port")] public string Station1ComPort { get; set; } = "COM3"; [JsonProperty("station1_timeout")] public int Station1TimeoutMs { get; set; } = 5000; // 加载方法 public static StationConfig Load(string path) { return JsonConvert.DeserializeObject<StationConfig>(File.ReadAllText(path)); } } // 在控制器中使用 var config = StationConfig.Load("station_config.json"); _comPort = new SerialPort(config.Station1ComPort);5. 改造后的架构优势验证
5.1 可维护性提升对比
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 平均修改时间 | 45分钟 | 15分钟 |
| 回归测试通过率 | 72% | 93% |
| 代码重复率 | 38% | 6% |
5.2 典型业务场景示例
工位异常处理流程的改进:
// Station1Controller.cs public override bool HandleError(ErrorType error) { try { _logger.LogError($"工位1发生异常:{error}"); // 停止所有设备 _motionController.EmergencyStop(); _ioController.ResetOutputs(); // 更新UI状态 MainForm.Instance.SetStation1LED(Color.Red); EventBus.Publish(new StationFaultEvent { StationId = 1 }); return RecoveryProcedures.TryRecover(error); } catch (Exception ex) { _logger.LogCritical(ex, "异常处理过程中发生错误"); return false; } }这种架构下,每个工位的异常处理可以保持独立逻辑,同时通过事件总线通知系统其他模块。