C#中无需为可修改代码写Adapter类,仅当对接不可更改的旧类或第三方库时才必须使用;应优先重构接口或统一抽象,避免无谓增加维护成本。

直接说结论:C#中不用写 Adapter 类来“适配接口”,除非你面对的是**已发布、不可修改的旧类或第三方库**;否则,优先重构目标接口或统一抽象,硬套适配器反而增加维护成本。
什么时候必须用对象适配器(Adapter 类)
典型场景是对接无法改动的外部组件,比如:
- 你引用了某个 NuGet 包里的
LegacyLogger类,它只有LogToConsole(string msg)方法,但你的业务层依赖的是ILogger.Log(string level, string message) - Unity 项目里要复用一个老 Asset 的
AudioPlayerV1,它暴露的是PlaySoundById(int id),而新 UI 系统只认IPlayable.Play(string clipName) - 调用 Windows COM 组件返回的
IDispatch对象,但你需要把它当ISerializable用
这些情况下,你不能改源码,也不能让别人改接口——只能自己包一层 Adapter。
ITarget 接口设计比 Adapter 实现更重要
很多团队一上来就写 Adapter,结果发现后续扩展困难。真正关键的是定义好 ITarget:
-
ITarget必须是**窄接口**:只声明当前客户端真正需要的方法,不要照搬 Adaptee 的全部能力。例如不要定义ITarget包含Connect()、Disconnect()、GetStatus(),如果客户端只调ExecuteQuery() - 避免在
ITarget中暴露 Adaptee 特有概念,比如把MySQLDatabase的ConnectionString暴露成ITarget.ConnectionString,这会让适配器变成“透明搬运工”,失去抽象价值 - 如果多个 Adaptee 共享行为(如重试、日志、超时),把这些逻辑提到
Adapter基类或装饰器里,而不是每个子类重复写
别踩 Adapter 构造函数注入的坑
对象适配器靠组合,但构造方式直接影响可测性与生命周期管理:
- 用构造函数参数传入
Adaptee实例最安全,但要注意:如果Adaptee是有状态的(比如持有一个未关闭的SqlConnection),Adapter就不能被当成无状态服务注册为Singleton - 避免在
Adapter内部 newAdaptee(如_adaptee = new Adaptee()),这会锁死依赖,无法 mock 测试,也违背控制反转原则 - Unity 或 ASP.NET Core 中注册
Adapter时,注意Adaptee的生命周期是否匹配:若Adaptee是Transient,Adapter不宜注册为Singleton,否则可能持有过期实例
性能敏感路径慎用适配器
每次调用 ITarget.Request() 都多一次虚方法跳转 + 成员字段访问,看似微小,但在高频循环(如每帧调用的 Unity 行为、高频日志写入)中会累积开销:
- 简单转发(如
_adaptee.SpecificRequest())一般没问题;但若适配逻辑含字符串拼接、JSON 序列化、正则匹配等,就得评估是否值得封装成Adapter - 若 Adaptee 方法本身很轻量(如只是设置一个布尔值),考虑直接在调用点做转换,比引入
Adapter更直白 - Unity 中尤其注意:
Adapter若继承自MonoBehaviour,且被挂载到 GameObject 上,它的Awake()/Start()执行时机和生命周期会受引擎调度影响,和纯 C#Adapter行为不一致
最常被忽略的一点:适配器不是设计起点,而是妥协终点。先确认能不能说服对方改接口、能不能用 dynamic 或 Reflection 临时绕过、甚至用 source generator 自动生成适配代码——再决定手写 Adapter。


















