多态是对象编程的基础。其中一个原则就是要将共用特性抽取到基类。但开发中需求总是会变:如果子类需要新增方法怎么办?难道依次修改基类和其他子类吗?
当然不是。应该遵守开闭原则(OCP),使用特化子类。
例如以下三个类是标准多态结构。
csharp
// 接口
public interface ICommunication
{
void Send(byte[] data);
}
// TCP 实现
public class TcpClientComm : ICommunication
{
public void Send(byte[] data) { /* TCP 实现 */ }
}
// 串口实现
public class SerialPortComm : ICommunication
{
public void Send(byte[] data) { /* 串口实现 */ }
}
如果 SerialPortComm 需要增加新方法怎么办?
答案是直接添加。但在调用时,将其转为子类 SerialPortComm 使用,如下图:
csharp
// 实现特有的发送字符函数
public class SerialPortComm : ICommunication
{
public void Send(byte[] data) { /* ... */ }
public void SendString(string data) { /* 串口特有逻辑 */ }
}
// 调用
// C# 9.0 以上的模式匹配(最推荐)
public void ProcessCommand(ICommunication comm)
{
// 通用部分:所有设备都发字节
comm.Send(new byte[] { 0x01, 0x02 });
// 特殊部分:仅当该设备"具备字符串能力"时才执行
if (comm is IStringCommunication stringable) // 这里不转具体类,转的是扩展接口
{
stringable.SendString("AT+RESET");
}
}
扩展 1
怎么避免在调用时写太多if...else... ?
答案是动态调度,好处是调用方(Main函数)一行代码都不用改。
csharp
public class CommandProcessor
{
// 针对 TCP 的默认处理(字节流)
private void ExecuteInternal(ICommunication comm)
=> comm.Send(new byte[] { 0x00 });
// 针对串口的特殊处理(重载)
private void ExecuteInternal(SerialComm comm)
=> comm.SendString("Hello");
// 对外暴露的唯一入口,利用 dynamic 自动分流
public void Execute(ICommunication comm)
{
// 核心技巧:把 comm 转为 dynamic,编译器会推迟绑定
// 运行时自动寻找参数类型最匹配的 ExecuteInternal
ExecuteInternal((dynamic)comm);
}
}
var processor = new CommandProcessor();
processor.Execute(new SerialComm()); // 输出:串口发字符串
processor.Execute(new TcpComm()); // 发送 0x00
扩展 2
怎么避免修改已有的子类?
为了符合开闭原则(OCP),可以使用适配器模式。将旧子类和新方法一起包装成一个新类来使用,如下:
csharp
// 不修改 Serial 原有的类
public class SerialStringAdapter : IStringCommunication
{
private readonly SerialPortComm _serial;
public SerialStringAdapter(SerialPortComm serial) { _serial = serial; }
public void Send(byte[] data)
{
_serial.Send(data);
}
public void SendString(string data)
{
// 内部转成字节调用原来的 Send
_serial.Send(Encoding.UTF8.GetBytes(data));
}
}