17. 适配器模式
约 1548 字大约 5 分钟
2026-03-13
定义
适配器模式(Adapter),适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
人话:适配器模式就像 姚明 刚去 NBA 打球时的情况。NBA 的球队教练和队友都习惯用英语指挥,比如要求球员 Attack(进攻) 和 Defense(防守)。而姚明原本更习惯用中文理解战术,比如 “进攻” 和 “防守”。
如果教练直接用英语下达战术,而姚明只能理解中文,那么双方之间就会出现沟通问题,比赛中执行战术就会很困难。这就好像在程序中,客户端调用的接口和已有类的方法不一致,两者之间无法直接配合。
而使用适配器模式之后,就相当于在姚明和教练之间安排了一位 翻译。教练依然只需要说 Attack 或 Defense,翻译会把这些指令转换成姚明能理解的 “进攻” 和 “防守”,再告诉姚明去执行。
在软件中也是一样:客户端仍然调用统一的接口(比如 Attack()、Defense()),而适配器内部会把这些调用转换成已有类真正的方法(比如 进攻()、防守())。这样客户端代码不需要改变,就能让原本接口不兼容的类正常工作。
适配器模式(Adapter)结构图
Target类——目标接口:
// Target(目标接口)
class Target
{
public virtual void Request()
{
Console.WriteLine("普通请求!");
}
}Adaptee类——需要适配的类:
// Adaptee(需要适配的类)
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("特殊请求!");
}
}Adapter类——适配器类:
// Adapter(适配器类)
class Adapter : Target
{
// 建立一个私有的 Adaptee 对象
private Adaptee adaptee = new Adaptee();
public override void Request()
{
// 把表面上调用 Request() 方法
// 转换成实际调用 SpecificRequest()
adaptee.SpecificRequest();
}
}客户端代码:
static void Main(string[] args)
{
// 对客户端来说,调用的就是 Target 的 Request()
Target target = new Adapter();
target.Request();
Console.Read();
}在软件开发中,也就是系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。
使用一个已经存在的类,但如果它的接口,也就是它的方法和你的要求不相同时,就应该考虑用适配器模式。两个类所做的事情相同或相似,但是具有不同的接口时要使用它。而且由于类都共享同一个接口,使得客户代码可以统一调用同一接口就行了,这样应该可以更简单、更直接、更紧凑。
其实用适配器模式也是无奈之举,很有点‘亡羊补牢’的感觉,没办法呀,是软件就有维护的一天,维护就有可能会因不同的开发人员、不同的产品、不同的厂家而造成功能类似而接口不同的情况,此时就是适配器模式大展拳脚的时候了。我们通常是在软件开发后期或维护期再考虑使用它,如果是在设计阶段,你有必要把类似的功能类的接口设计得不同吗?
公司内部,类和方法的命名应该有规范,最好前期就设计好,然后如果真的接口不相同时,首先不应该考虑用适配器,而是应该考虑通过重构统一接口。
在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同时就使用它。
那有没有设计之初就需要考虑用适配器模式的时候?比如公司设计一系统时考虑使用第三方开发组件,而这个组件的接口与我们自己的系统 接口是不相同的,而我们也完全没有必要为了迎合它而改动自己的接口,此时尽管是在开发的设计阶段,也是可以考虑用适配器模式来解决接口不同的问题。
举例
适配器模式举例图
ForeignCenter类——外籍中锋:
// 外籍中锋
class ForeignCenter
{
private string name;
// 外籍中锋类球员的姓名
public string Name
{
get { return name; }
set { name = value; }
}
// 表明“外籍中锋”只懂得中文“进攻”
public void 进攻()
{
Console.WriteLine("外籍中锋 {0} 进攻", name);
}
// 表明“外籍中锋”只懂得中文“防守”
public void 防守()
{
Console.WriteLine("外籍中锋 {0} 防守", name);
}
}Translator类——翻译者(适配器):
// 翻译者(适配器)
class Translator : Player
{
// 声明并实例化一个内部“外籍中锋”对象
private ForeignCenter wjzf = new ForeignCenter();
public Translator(string name) : base(name)
{
wjzf.Name = name;
}
// 翻译者将 Attack 翻译为“进攻”
public override void Attack()
{
wjzf.进攻();
}
// 翻译者将 Defense 翻译为“防守”
public override void Defense()
{
wjzf.防守();
}
}客户端代码:
static void Main(string[] args)
{
Player b = new Forwards("巴蒂尔");
b.Attack();
Player m = new Guards("麦克格雷迪");
m.Attack();
Player ym = new Translator("姚明");
ym.Attack();
ym.Defense();
Console.Read();
}运行结果:
前锋 巴蒂尔 进攻
后卫 麦克格雷迪 进攻
外籍中锋 姚明 进攻
外籍中锋 姚明 防守