在工控上位机开发中,单一硬件封装(如温度传感器)已无法满足复杂项目需求(多类型传感器、PLC、仪表共存)。继承作为面向对象的核心特性,能够实现通用硬件逻辑的复用,统一设备管理接口,大幅降低多类型硬件的开发与维护成本。本文将以工控场景为核心,详解继承在硬件封装中的实战应用,从基础抽象类设计到多子类扩展,实现 "一套接口管理所有设备"。
一、 继承的核心价值:工控硬件的 "通用逻辑复用"
工控领域的硬件(温度传感器、湿度传感器、压力传感器、串口 PLC)存在大量通用属性与通用操作,如果每个设备都单独封装,会产生大量冗余代码(如串口配置、连接 / 断开逻辑、状态反馈等)。
继承的核心价值在于:
- 抽取通用逻辑:将所有硬件的通用属性(设备 ID、端口号、波特率、连接状态)和通用方法(连接、断开、资源释放)抽取到 ** 基类(父类)** 中;
- 实现子类扩展:不同硬件(温度 / 湿度传感器)继承基类,只需实现自身独有的逻辑(如温度解析、湿度解析),无需重复编写通用代码;
- 统一管理接口:通过基类引用管理所有子类对象,实现 "不区分设备类型,统一调用方法",简化多设备管理逻辑。
工控硬件通用特性梳理
| 通用属性(所有硬件共享) | 通用操作(所有硬件共享) | 独有逻辑(不同硬件差异) |
|---|---|---|
| 设备唯一 ID(SensorId) | 设备连接(Connect) | 数据解析逻辑(温度 / 湿度 / 压力) |
| 串口名称(PortName) | 设备断开(Disconnect) | 专属指令组装(读取温度 / 写入 PLC 寄存器) |
| 波特率(BaudRate) | 资源释放(Dispose) | 数据类型转换(float 温度 /int 湿度) |
| 连接状态(IsConnected) | 状态反馈(StatusChanged) | 校准逻辑(温度校准 / 压力零点校准) |
| 异常信息(ErrorMsg) | 异常反馈(ErrorOccurred) | - |
二、 实战第一步:设计硬件抽象基类(SensorBase)
基类分为两种:普通基类 (可实例化)和抽象基类 (不可实例化,仅作为子类模板)。工控场景中,优先使用抽象基类(abstract class),因为 "传感器" 是一个抽象概念,只有具体的温度 / 湿度传感器才有实际意义,抽象基类可强制子类实现独有逻辑,避免接口不统一。
1. 抽象基类完整代码(通用逻辑封装)
cs
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
namespace UpperComputerOOPInherit
{
/// <summary>
/// 工控硬件抽象基类(所有串口设备的父类)
/// 特点:1. 封装通用属性与方法;2. 定义抽象方法,强制子类实现独有逻辑;3. 通过事件实现与UI层解耦
/// </summary>
public abstract class SensorBase : IDisposable
{
#region 1. 通用属性(所有串口硬件共享,子类可直接访问)
// 私有字段:通用硬件参数
private int _sensorId; // 设备唯一ID
private string _portName; // 串口名称
private int _baudRate; // 波特率
private bool _isConnected; // 连接状态
protected SerialPort _serialPort; // 串口对象(protected:子类可访问,外部不可访问)
protected CancellationTokenSource _cts; // 采集取消令牌(子类可访问)
protected Task _collectTask; // 采集任务(子类可访问)
// 公共只读属性:外部可访问,不可修改
public int SensorId => _sensorId;
public string PortName => _portName;
public int BaudRate => _baudRate;
public bool IsConnected => _isConnected;
// 保护属性:子类可修改,外部不可访问
protected bool IsCollecting { get; set; } = false; // 是否正在采集
#endregion
#region 2. 通用事件(所有硬件共享,统一反馈状态/数据/异常)
/// <summary>
/// 设备状态变更事件(连接/断开)
/// </summary>
public event Action<int, string, bool> StatusChanged;
/// <summary>
/// 异常发生事件
/// </summary>
public event Action<int, string> ErrorOccurred;
/// <summary>
/// 通用数据更新事件(携带设备ID和数据对象,适配所有硬件数据类型)
/// </summary>
public event Action<int, object> DataUpdated;
#endregion
#region 3. 构造函数(初始化通用硬件参数,子类调用)
/// <summary>
/// 基类构造函数:初始化串口硬件通用参数
/// </summary>
/// <param name="sensorId">设备唯一ID</param>
/// <param name="portName">串口名称(COM3/COM4等)</param>
/// <param name="baudRate">波特率(9600/115200等)</param>
public SensorBase(int sensorId, string portName, int baudRate)
{
_sensorId = sensorId;
_portName = portName;
_baudRate = baudRate;
_isConnected = false;
// 初始化通用串口配置(所有串口硬件共享)
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = baudRate,
Parity = Parity.None,
DataBits = 8,
StopBits = StopBits.One,
ReadTimeout = 1000,
WriteTimeout = 1000
};
_cts = new CancellationTokenSource();
// 绑定通用串口数据接收事件(子类可重写或复用)
_serialPort.DataReceived += SerialPort_DataReceived;
}
#endregion
#region 4. 通用方法(所有硬件共享,子类可直接使用,无需重复编写)
/// <summary>
/// 通用连接方法(所有串口硬件共用同一套连接逻辑)
/// </summary>
/// <returns>连接是否成功</returns>
public virtual bool Connect()
{
try
{
if (!_isConnected && !_serialPort.IsOpen)
{
_serialPort.Open();
_isConnected = true;
// 触发状态变更事件
StatusChanged?.Invoke(_sensorId, _portName, _isConnected);
OnConnectSuccess(); // 子类可重写该方法,实现连接成功后的专属逻辑
return true;
}
return _isConnected;
}
catch (Exception ex)
{
string errorMsg = $"设备{_sensorId}({_portName})连接失败:{ex.Message}";
ErrorOccurred?.Invoke(_sensorId, errorMsg);
return false;
}
}
/// <summary>
/// 通用断开方法(所有串口硬件共用同一套断开逻辑)
/// </summary>
/// <returns>断开是否成功</returns>
public virtual bool Disconnect()
{
try
{
// 通用逻辑:先停止采集
StopCollect();
if (_isConnected && _serialPort.IsOpen)
{
_serialPort.Close();
_isConnected = false;
// 触发状态变更事件
StatusChanged?.Invoke(_sensorId, _portName, _isConnected);
OnDisconnectSuccess(); // 子类可重写该方法,实现断开后的专属逻辑
return true;
}
return !_isConnected;
}
catch (Exception ex)
{
string errorMsg = $"设备{_sensorId}({_portName})断开失败:{ex.Message}";
ErrorOccurred?.Invoke(_sensorId, errorMsg);
return false;
}
}
/// <summary>
/// 通用启动采集方法(所有硬件共用同一套采集任务逻辑)
/// </summary>
/// <param name="interval">采集间隔(毫秒)</param>
public virtual void StartCollect(int interval = 1000)
{
if (!_isConnected)
{
ErrorOccurred?.Invoke(_sensorId, $"设备{_sensorId}未连接,无法启动采集");
return;
}
if (!IsCollecting && (_collectTask == null || _collectTask.IsCompleted))
{
IsCollecting = true;
_cts = new CancellationTokenSource();
CancellationToken token = _cts.Token;
// 通用后台采集任务(子类只需实现具体的数据读取逻辑)
_collectTask = Task.Run(() =>
{
while (!token.IsCancellationRequested && IsCollecting)
{
ReadDataManually(); // 调用抽象方法,由子类实现具体逻辑
Thread.Sleep(interval);
}
}, token);
}
}
/// <summary>
/// 通用停止采集方法(所有硬件共用)
/// </summary>
public virtual void StopCollect()
{
if (IsCollecting)
{
IsCollecting = false;
if (_cts != null && !_cts.IsCancellationRequested)
{
_cts.Cancel();
}
}
}
/// <summary>
/// 通用资源释放方法(所有硬件共用,防止内存泄漏/端口占用)
/// </summary>
public virtual void Dispose()
{
Disconnect(); // 先断开连接
_serialPort?.Dispose(); // 释放串口资源
_cts?.Dispose(); // 释放取消令牌资源
GC.SuppressFinalize(this);
}
#endregion
#region 5. 抽象方法(强制子类实现,保证接口统一,体现独有逻辑)
/// <summary>
/// 抽象方法:手动读取数据(子类必须实现,对应各自的硬件数据读取逻辑)
/// </summary>
/// <returns>读取到的具体数据(object类型,适配所有数据类型)</returns>
public abstract object ReadDataManually();
/// <summary>
/// 抽象方法:组装硬件专属指令(子类必须实现,如温度读取指令、湿度读取指令)
/// </summary>
/// <returns>专属指令字节数组</returns>
protected abstract byte[] BuildHardwareCmd();
#endregion
#region 6. 虚方法(子类可选择性重写,扩展通用逻辑)
/// <summary>
/// 虚方法:连接成功后的扩展逻辑(子类可重写,默认无实现)
/// </summary>
protected virtual void OnConnectSuccess()
{
// 通用逻辑可在此扩展,子类也可重写覆盖
}
/// <summary>
/// 虚方法:断开成功后的扩展逻辑(子类可重写,默认无实现)
/// </summary>
protected virtual void OnDisconnectSuccess()
{
// 通用逻辑可在此扩展,子类也可重写覆盖
}
/// <summary>
/// 通用串口数据接收事件(子类可重写,实现专属数据解析)
/// </summary>
protected virtual void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// 通用数据接收逻辑:读取字节流(子类可重写该方法,实现专属解析)
try
{
int byteCount = _serialPort.BytesToRead;
byte[] recvData = new byte[byteCount];
_serialPort.Read(recvData, 0, byteCount);
// 子类重写时,可在此处添加专属数据解析逻辑
OnDataReceived(recvData);
}
catch (Exception ex)
{
string errorMsg = $"设备{_sensorId}数据接收失败:{ex.Message}";
ErrorOccurred?.Invoke(_sensorId, errorMsg);
}
}
/// <summary>
/// 虚方法:数据接收后的扩展逻辑(子类可重写,实现专属解析)
/// </summary>
/// <param name="recvData">接收的字节数组</param>
protected virtual void OnDataReceived(byte[] recvData)
{
// 子类重写该方法,实现数据解析
}
#endregion
}
}
2. 基类设计关键要点
- 访问修饰符合理分配 :
private:通用字段(_sensorId、_isConnected),仅基类内部访问,防止外部非法修改;protected:串口对象(_serialPort)、取消令牌(_cts),子类可直接访问,无需通过属性传递,简化子类开发;public:通用方法(Connect、Disconnect)、只读属性(SensorId、IsConnected),外部调用者可访问,统一设备操作接口;abstract:抽象方法(ReadDataManually、BuildHardwareCmd),强制子类实现,保证所有设备接口统一;virtual:虚方法(OnConnectSuccess、SerialPort_DataReceived),子类可选择性重写,兼顾通用逻辑与子类扩展。
- 事件统一化 :定义
StatusChanged(状态变更)、ErrorOccurred(异常反馈)、DataUpdated(数据更新)三个通用事件,所有子类共享同一套事件机制,UI 层可统一订阅,无需区分设备类型。 - 资源安全保障 :实现
IDisposable接口,提供通用Dispose方法,自动释放串口、取消令牌资源,防止程序退出后端口占用或内存泄漏。
三、 实战第二步:子类继承基类,实现专属硬件逻辑
基于SensorBase抽象基类,我们分别实现 ** 温度传感器(TemperatureSensor)和湿度传感器(HumiditySensor)** 两个子类,只需关注自身独有逻辑(数据解析、指令组装),无需重复编写通用逻辑。
1. 子类 1:温度传感器(TemperatureSensor)
cs
using System;
namespace UpperComputerOOPInherit
{
/// <summary>
/// 温度传感器子类(继承自SensorBase抽象基类)
/// 只需实现独有逻辑:温度指令组装、温度数据解析
/// </summary>
public class TemperatureSensor : SensorBase
{
// 独有字段:当前温度值
private float _currentTemp;
// 独有属性:当前温度(外部只读)
public float CurrentTemp => _currentTemp;
/// <summary>
/// 子类构造函数:调用基类构造函数,初始化通用参数
/// </summary>
/// <param name="sensorId">传感器ID</param>
/// <param name="portName">串口名称</param>
/// <param name="baudRate">波特率</param>
public TemperatureSensor(int sensorId, string portName, int baudRate)
: base(sensorId, portName, baudRate) // 必须调用基类构造函数
{
_currentTemp = 0.0f;
}
#region 实现抽象方法(独有逻辑:强制实现)
/// <summary>
/// 实现抽象方法:手动读取温度数据
/// </summary>
/// <returns>当前温度(object类型,可自动装箱)</returns>
public override object ReadDataManually()
{
try
{
if (!IsConnected)
{
throw new Exception("温度传感器未连接,无法读取温度");
}
// 调用子类独有指令组装方法
byte[] readCmd = BuildHardwareCmd();
// 复用基类的串口对象,无需重复初始化
_serialPort.Write(readCmd, 0, readCmd.Length);
// 返回当前温度(object类型,适配基类通用事件)
return _currentTemp;
}
catch (Exception ex)
{
string errorMsg = $"温度传感器{SensorId}读取失败:{ex.Message}";
ErrorOccurred?.Invoke(SensorId, errorMsg);
return 0.0f;
}
}
/// <summary>
/// 实现抽象方法:组装温度传感器专属Modbus-RTU指令
/// </summary>
/// <returns>温度读取指令字节数组</returns>
protected override byte[] BuildHardwareCmd()
{
// 温度传感器独有指令:Modbus-RTU 03功能码,读取温度寄存器
byte[] cmd = new byte[8];
cmd[0] = (byte)SensorId; // 从站地址=传感器ID
cmd[1] = 0x03; // 03功能码:读取保持寄存器
cmd[2] = 0x00; // 起始寄存器高8位
cmd[3] = 0x00; // 起始寄存器低8位
cmd[4] = 0x00; // 寄存器数量高8位
cmd[5] = 0x01; // 寄存器数量低8位
// CRC16校验(工控标准校验,固定值仅作示例,可替换为真实CRC算法)
cmd[6] = 0x84;
cmd[7] = 0x0A;
return cmd;
}
#endregion
#region 重写虚方法(扩展逻辑:选择性重写)
/// <summary>
/// 重写虚方法:数据接收后,解析温度数据(独有逻辑)
/// </summary>
/// <param name="recvData">接收的字节数组</param>
protected override void OnDataReceived(byte[] recvData)
{
base.OnDataReceived(recvData); // 可选:调用基类通用逻辑
// 温度传感器独有解析逻辑:4字节转float温度值
if (recvData.Length >= 4)
{
byte[] tempBytes = new byte[4];
Array.Copy(recvData, 2, tempBytes, 0, 4);
// 工控必备:处理字节序(硬件大端序→系统小端序)
if (BitConverter.IsLittleEndian)
{
Array.Reverse(tempBytes);
}
// 更新子类独有字段:当前温度
_currentTemp = BitConverter.ToSingle(tempBytes, 0);
// 触发基类通用数据更新事件,UI层统一接收
DataUpdated?.Invoke(SensorId, _currentTemp);
}
}
/// <summary>
/// 重写虚方法:温度传感器连接成功后的专属逻辑(如校准初始化)
/// </summary>
protected override void OnConnectSuccess()
{
// 子类专属逻辑:连接成功后,初始化温度校准参数
string msg = $"温度传感器{SensorId}连接成功,已初始化校准参数";
Console.WriteLine(msg);
// 可在此添加校准逻辑
}
#endregion
}
}
2. 子类 2:湿度传感器(HumiditySensor)
cs
using System;
namespace UpperComputerOOPInherit
{
/// <summary>
/// 湿度传感器子类(继承自SensorBase抽象基类)
/// 只需实现独有逻辑:湿度指令组装、湿度数据解析
/// </summary>
public class HumiditySensor : SensorBase
{
// 独有字段:当前湿度值
private int _currentHumidity;
// 独有属性:当前湿度(外部只读)
public int CurrentHumidity => _currentHumidity;
/// <summary>
/// 子类构造函数:调用基类构造函数,初始化通用参数
/// </summary>
/// <param name="sensorId">传感器ID</param>
/// <param name="portName">串口名称</param>
/// <param name="baudRate">波特率</param>
public HumiditySensor(int sensorId, string portName, int baudRate)
: base(sensorId, portName, baudRate) // 调用基类构造函数
{
_currentHumidity = 0;
}
#region 实现抽象方法(独有逻辑:强制实现)
/// <summary>
/// 实现抽象方法:手动读取湿度数据
/// </summary>
/// <returns>当前湿度(object类型)</returns>
public override object ReadDataManually()
{
try
{
if (!IsConnected)
{
throw new Exception("湿度传感器未连接,无法读取湿度");
}
// 调用子类独有指令组装方法
byte[] readCmd = BuildHardwareCmd();
// 复用基类串口对象
_serialPort.Write(readCmd, 0, readCmd.Length);
return _currentHumidity;
}
catch (Exception ex)
{
string errorMsg = $"湿度传感器{SensorId}读取失败:{ex.Message}";
ErrorOccurred?.Invoke(SensorId, errorMsg);
return 0;
}
}
/// <summary>
/// 实现抽象方法:组装湿度传感器专属Modbus-RTU指令
/// </summary>
/// <returns>湿度读取指令字节数组</returns>
protected override byte[] BuildHardwareCmd()
{
// 湿度传感器独有指令:Modbus-RTU 03功能码,读取湿度寄存器
byte[] cmd = new byte[8];
cmd[0] = (byte)SensorId; // 从站地址=传感器ID
cmd[1] = 0x03; // 03功能码:读取保持寄存器
cmd[2] = 0x00; // 起始寄存器高8位
cmd[3] = 0x01; // 起始寄存器低8位(与温度传感器不同,独有逻辑)
cmd[4] = 0x00; // 寄存器数量高8位
cmd[5] = 0x01; // 寄存器数量低8位
// CRC16校验
cmd[6] = 0xC4;
cmd[7] = 0x0B;
return cmd;
}
#endregion
#region 重写虚方法(扩展逻辑:选择性重写)
/// <summary>
/// 重写虚方法:数据接收后,解析湿度数据(独有逻辑)
/// </summary>
/// <param name="recvData">接收的字节数组</param>
protected override void OnDataReceived(byte[] recvData)
{
base.OnDataReceived(recvData);
// 湿度传感器独有解析逻辑:2字节转int湿度值
if (recvData.Length >= 4)
{
// 拼接高低位字节
_currentHumidity = (recvData[2] << 8) | recvData[3];
// 触发基类通用数据更新事件
DataUpdated?.Invoke(SensorId, _currentHumidity);
}
}
#endregion
}
}
子类设计关键要点
- 构造函数必须调用基类 :子类通过
: base(sensorId, portName, baudRate)调用基类构造函数,初始化通用参数(设备 ID、串口信息),无需重复编写初始化逻辑。 - 强制实现抽象方法 :必须实现
ReadDataManually(数据读取)和BuildHardwareCmd(指令组装)两个抽象方法,保证所有子类接口统一,基类可通过抽象方法调用子类独有逻辑。 - 选择性重写虚方法 :针对
OnDataReceived(数据解析)、OnConnectSuccess(连接后扩展)等虚方法,子类可根据需求重写,实现独有逻辑,不影响基类通用功能。 - 复用基类资源 :子类可直接访问基类的
_serialPort(串口对象)、IsConnected(连接状态)等protected成员,无需重复创建,大幅减少冗余代码。
四、 实战第三步:基于继承的多设备统一管理
通过继承,所有硬件都实现了SensorBase基类的统一接口,上位机可通过基类引用管理所有子类对象,无需区分设备类型(温度 / 湿度传感器),实现 "一套代码管理所有设备"。
1. 多设备统一管理代码
cs
using System;
using System.Collections.Generic;
namespace UpperComputerOOPInherit
{
class Program
{
// 用基类列表存储所有子类对象,实现统一管理
private static List<SensorBase> _sensorList = new List<SensorBase>();
static void Main(string[] args)
{
// 1. 创建不同类型传感器对象(子类实例),添加到基类列表
TemperatureSensor tempSensor1 = new TemperatureSensor(1, "COM3", 9600);
TemperatureSensor tempSensor2 = new TemperatureSensor(2, "COM4", 9600);
HumiditySensor humiSensor1 = new HumiditySensor(3, "COM5", 9600);
HumiditySensor humiSensor2 = new HumiditySensor(4, "COM6", 9600);
// 基类列表存储子类对象(多态特性)
_sensorList.Add(tempSensor1);
_sensorList.Add(tempSensor2);
_sensorList.Add(humiSensor1);
_sensorList.Add(humiSensor2);
// 2. 统一订阅所有设备的通用事件(无需区分设备类型)
foreach (var sensor in _sensorList)
{
sensor.StatusChanged += OnSensorStatusChanged;
sensor.DataUpdated += OnSensorDataUpdated;
sensor.ErrorOccurred += OnSensorErrorOccurred;
}
// 3. 统一操作所有设备(连接、启动采集)
Console.WriteLine("开始统一连接所有设备...");
foreach (var sensor in _sensorList)
{
sensor.Connect(); // 统一调用基类Connect方法
}
Console.WriteLine("\n开始统一启动所有设备采集...");
foreach (var sensor in _sensorList)
{
sensor.StartCollect(1000); // 统一调用基类StartCollect方法
}
// 保持程序运行
Console.WriteLine("\n多设备采集已启动,按任意键停止...");
Console.ReadKey();
// 4. 统一操作所有设备(停止采集、断开连接、释放资源)
Console.WriteLine("\n开始统一停止采集并断开所有设备...");
foreach (var sensor in _sensorList)
{
sensor.StopCollect(); // 统一调用基类StopCollect方法
sensor.Disconnect(); // 统一调用基类Disconnect方法
sensor.Dispose(); // 统一调用基类Dispose方法
}
_sensorList.Clear();
Console.WriteLine("所有设备已断开并释放资源,程序退出...");
}
#region 统一事件处理逻辑(无需区分设备类型)
/// <summary>
/// 所有设备的状态变更统一处理
/// </summary>
private static void OnSensorStatusChanged(int sensorId, string portName, bool isConnected)
{
string status = isConnected ? "已连接" : "已断开";
Console.WriteLine($"【状态变更】设备{sensorId}({portName}):{status}");
}
/// <summary>
/// 所有设备的数据更新统一处理
/// </summary>
private static void OnSensorDataUpdated(int sensorId, object data)
{
// 区分设备类型(可选),显示对应数据
var tempSensor = _sensorList.Find(s => s is TemperatureSensor && s.SensorId == sensorId);
var humiSensor = _sensorList.Find(s => s is HumiditySensor && s.SensorId == sensorId);
if (tempSensor != null)
{
float temp = (float)data;
Console.WriteLine($"【数据更新】温度传感器{sensorId}:{temp:F2}℃");
}
else if (humiSensor != null)
{
int humi = (int)data;
Console.WriteLine($"【数据更新】湿度传感器{sensorId}:{humi}%RH");
}
}
/// <summary>
/// 所有设备的异常统一处理
/// </summary>
private static void OnSensorErrorOccurred(int sensorId, string errorMsg)
{
Console.WriteLine($"【异常提示】设备{sensorId}:{errorMsg}");
}
#endregion
}
}
2. 多设备管理优势(继承带来的核心价值)
- 接口统一 :所有设备都通过基类的
Connect、Disconnect、StartCollect等方法操作,无需针对温度 / 湿度传感器编写不同的操作逻辑,简化代码复杂度。 - 扩展便捷 :新增设备类型(如压力传感器
PressureSensor)时,只需继承SensorBase基类,实现抽象方法,即可直接加入基类列表,无需修改现有管理代码(符合 "开闭原则")。 - 维护成本低 :若通用逻辑需要修改(如串口连接超时时间调整),只需修改
SensorBase基类的Connect方法,所有子类自动生效,无需逐个修改子类。 - 多态特性体现 :基类列表存储子类对象,调用基类方法时,自动执行子类的实现逻辑(如
ReadDataManually),实现 "一个接口,多种实现"。
五、 继承在工控开发中的进阶应用
1. 多层继承:抽象基类→通用串口设备→具体设备
对于更复杂的项目(串口设备 + 网口设备共存),可采用多层继承:
plaintext
// 第一层:顶级抽象基类(所有设备通用)
public abstract class DeviceBase { /* 设备ID、状态、事件等通用属性 */ }
// 第二层:串口设备基类(继承DeviceBase,封装串口通用逻辑)
public abstract class SerialDeviceBase : DeviceBase { /* 串口配置、串口读写等逻辑 */ }
// 第三层:网口设备基类(继承DeviceBase,封装TCP/UDP通用逻辑)
public abstract class NetworkDeviceBase : DeviceBase { /* IP、端口、网络读写等逻辑 */ }
// 第四层:具体设备(继承对应二级基类)
public class TemperatureSensor : SerialDeviceBase { /* 温度传感器独有逻辑 */ }
public class PlcTcp : NetworkDeviceBase { /* 网口PLC独有逻辑 */ }
2. 抽象类 vs 接口:工控场景选型
| 类型 | 适用场景 | 工控应用示例 |
|---|---|---|
| 抽象类(abstract) | 存在大量通用属性 / 方法,需要复用代码 | 所有串口传感器(共享串口配置、连接逻辑) |
| 接口(interface) | 仅需统一接口,无通用代码复用,支持多实现 | 可同时支持串口 / 网口的设备(IConnectable接口) |
示例:IConnectable接口,统一所有设备的连接接口
cs
/// <summary>
/// 连接接口:统一串口/网口设备的连接接口
/// </summary>
public interface IConnectable
{
bool Connect();
bool Disconnect();
bool IsConnected { get; }
}
// 串口传感器继承抽象类+实现接口
public class TemperatureSensor : SerialDeviceBase, IConnectable { }
// 网口PLC继承抽象类+实现接口
public class PlcTcp : NetworkDeviceBase, IConnectable { }
六、 总结
- 继承核心价值 :在 C# 上位机工控开发中,继承的核心是通用逻辑复用 与接口统一,将所有硬件的通用属性 / 方法封装到抽象基类,子类只需实现独有逻辑,大幅减少冗余代码。
- 基类设计要点 :优先使用抽象基类,合理分配访问修饰符(
private/protected/public),定义通用事件与虚方法,提供资源释放机制,兼顾复用性与扩展性。 - 子类设计要点 :通过
: base()调用基类构造函数,强制实现抽象方法(保证接口统一),选择性重写虚方法(实现独有逻辑),复用基类的protected成员。 - 多设备管理:通过基类列表存储子类对象,利用多态特性实现统一操作与事件处理,新增设备类型无需修改现有代码,符合工业级上位机的扩展性要求。
- 实战落地 :本文提供的
SensorBase基类及子类代码可直接复用,只需根据实际硬件协议修改指令组装与数据解析逻辑,即可快速对接真实工控设备。