1. 适配器模式简介
适配器模式用于解决接口不兼容问题,将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。适配器模式主要分为两类:
类适配器模式:通过继承适配者类来实现适配。
对象适配器模式:通过组合适配者对象来实现适配。
2. 定义接口
在 Interfaces 文件夹中定义目标接口(Target)和需要适配的接口(Adaptee):
cs
// IPlayer.cs - Target接口
public interface IPlayer
{
void Attack(); // 玩家角色攻击
void Move(); // 玩家角色移动
}
cs
// Robot.cs - Adaptee类,来自第三方库,接口不兼容
public class Robot
{
public void FireWeapon()
{
Console.WriteLine("机器人发射武器!");
}
public void Walk()
{
Console.WriteLine("机器人行走!");
}
}
2.1 定义并理解IPlayer接口和Robot类的功能
2.1.1. IPlayer 接口的功能
IPlayer 接口定义了玩家角色的基本行为,包括攻击和移动。具体方法如下:
-
Attack():表示玩家角色执行攻击动作。 -
Move():表示玩家角色执行移动动作。
IPlayer 接口是目标接口,客户端代码期望通过调用 Attack() 和 Move() 来控制玩家角色的行为。
2.1.2 Robot 类的功能
Robot 类是一个需要适配的类,它来自第三方库,提供了机器人角色的行为,但与 IPlayer 接口不兼容。具体方法如下:
-
FireWeapon():表示机器人发射武器。 -
Walk():表示机器人行走。
Robot 类的功能与 IPlayer 接口的功能相似,但方法名称和行为实现不同。
2.1.3 不兼容的部分
IPlayer 接口和 Robot 类的功能虽然相似,但接口定义不一致,导致它们无法直接协同工作。具体不兼容的部分如下:
| 功能 | IPlayer 接口 |
Robot 类 |
不兼容的原因 |
|---|---|---|---|
| 攻击行为 | Attack() |
FireWeapon() |
方法名称不同,IPlayer 期望调用 Attack(),而 Robot 提供的是 FireWeapon()。 |
| 移动行为 | Move() |
Walk() |
方法名称不同,IPlayer 期望调用 Move(),而 Robot 提供的是 Walk()。 |
2.1.4 不兼容的示例
假设客户端代码期望通过 IPlayer 接口控制角色行为:
cs
IPlayer player = new Player(); // 假设Player是IPlayer的实现类
player.Attack(); // 期望执行攻击
player.Move(); // 期望执行移动
但如果我们直接使用 Robot 类:
cs
Robot robot = new Robot();
robot.FireWeapon(); // 与Attack()不兼容
robot.Walk(); // 与Move()不兼容
由于方法名称和行为不一致,客户端代码无法直接使用 Robot 类。
2.1.5 适配器模式的作用
适配器模式的作用是将 Robot 类的接口适配到 IPlayer 接口,使得客户端代码可以通过 IPlayer 接口调用 Robot 类的方法。具体实现如下:
-
类适配器模式 :通过继承
Robot类并实现IPlayer接口,将Attack()映射到FireWeapon(),将Move()映射到Walk()。 -
对象适配器模式 :通过组合
Robot类的实例并实现IPlayer接口,将Attack()委托给FireWeapon(),将Move()委托给Walk()。
2.1.6 总结
-
IPlayer接口 :定义了客户端期望的接口(Attack()和Move())。 -
Robot类 :提供了实际的功能(FireWeapon()和Walk()),但接口与IPlayer不兼容。 -
不兼容的部分 :方法名称不同(
Attack()vsFireWeapon(),Move()vsWalk())。 -
适配器模式的作用 :通过适配器将
Robot类的接口转换为IPlayer接口,解决接口不兼容问题。
3 . 类适配器模式的实现
任务目标
-
实现类适配器
RobotAdapter,确保适配后的Attack()方法调用FireWeapon(),Move()方法调用Walk()。 -
理解继承的方式如何让适配器类直接复用
Robot类的方法。
实现步骤
1. 创建类适配器 RobotAdapter
在 Adapters 文件夹中创建 RobotAdapter 类,继承 Robot 类并实现 IPlayer 接口。
cs
// RobotAdapter.cs - 类适配器类,继承Adaptee并实现Target接口
public class RobotAdapter : Robot, IPlayer
{
// 将Target接口的Attack适配为Adaptee的FireWeapon
public void Attack()
{
FireWeapon(); // 调用Robot的FireWeapon
}
// 将Target接口的Move适配为Adaptee的Walk
public void Move()
{
Walk(); // 调用Robot的Walk
}
}
2. 代码解析
-
继承
Robot类 :RobotAdapter继承了Robot类,因此可以直接使用Robot类的方法(如FireWeapon()和Walk())。 -
实现
IPlayer接口 :RobotAdapter实现了IPlayer接口,因此必须提供Attack()和Move()方法。 -
方法适配:
-
Attack()方法内部调用FireWeapon(),将IPlayer的Attack()适配为Robot的FireWeapon()。 -
Move()方法内部调用Walk(),将IPlayer的Move()适配为Robot的Walk()。
-
3. 测试类适配器
在 Program.cs 中编写测试代码,验证 RobotAdapter 的功能。
cs
class Program
{
static void Main(string[] args)
{
// 使用类适配器将Robot适配为Player
IPlayer player = new RobotAdapter();
// 调用适配后的接口
player.Attack(); // 实际调用的是Robot的FireWeapon
player.Move(); // 实际调用的是Robot的Walk
}
}
4. 运行结果
运行程序后,输出如下:
cs
机器人发射武器!
机器人行走!

理解继承的方式如何让适配器类直接复用 Robot 类的方法
-
继承的作用:
-
RobotAdapter继承了Robot类,因此可以直接使用Robot类的所有公共方法(如FireWeapon()和Walk())。 -
继承使得
RobotAdapter无需重新实现Robot类的功能,直接复用其方法。
-
-
复用方法的体现:
-
在
RobotAdapter中,Attack()方法直接调用FireWeapon(),Move()方法直接调用Walk()。 -
这些方法的具体实现来自
Robot类,RobotAdapter只是将其适配到IPlayer接口。
-
-
优点:
-
代码简洁:无需重新实现
Robot类的功能。 -
直接复用:通过继承,
RobotAdapter可以直接使用Robot类的方法。
-
-
缺点:
-
灵活性较低:类适配器只能适配一个
Adaptee类(即Robot类),无法适配多个Adaptee类。 -
继承关系可能导致类层次复杂。
-
总结
-
实现类适配器 :通过继承
Robot类并实现IPlayer接口,将Attack()适配为FireWeapon(),将Move()适配为Walk()。 -
继承的作用 :继承使得
RobotAdapter可以直接复用Robot类的方法,无需重新实现。 -
运行结果 :程序输出
机器人发射武器!和机器人行走!,证明适配器模式功能实现。 -
理解继承的复用 :继承是类适配器模式的核心机制,通过继承直接复用
Adaptee类的方法,但灵活性较低。
4. 对象适配器模式的实现
4.1 任务目标
-
编写对象适配器
RobotObjectAdapter,理解通过组合的方式如何适配接口。 -
理解对象适配器模式如何比类适配器模式更灵活。
4.2 实现步骤
1. 创建对象适配器 RobotObjectAdapter
在 Adapters 文件夹中创建 RobotObjectAdapter 类,通过组合 Robot 类的实例并实现 IPlayer 接口。
cs
// RobotObjectAdapter.cs - 对象适配器类,组合Adaptee对象
public class RobotObjectAdapter : IPlayer
{
private Robot _robot; // 持有Adaptee的实例
// 构造函数中传入Adaptee对象
public RobotObjectAdapter(Robot robot)
{
_robot = robot;
}
// 实现Target接口,将Attack适配为FireWeapon
public void Attack()
{
_robot.FireWeapon(); // 调用Adaptee的方法
}
// 实现Target接口,将Move适配为Walk
public void Move()
{
_robot.Walk(); // 调用Adaptee的方法
}
}
2. 代码解析
-
组合
Robot实例 :RobotObjectAdapter内部持有一个Robot类的实例(_robot),通过组合的方式实现适配。 -
实现
IPlayer接口 :RobotObjectAdapter实现了IPlayer接口,因此必须提供Attack()和Move()方法。 -
方法适配:
-
Attack()方法内部调用_robot.FireWeapon(),将IPlayer的Attack()适配为Robot的FireWeapon()。 -
Move()方法内部调用_robot.Walk(),将IPlayer的Move()适配为Robot的Walk()。
-
4.3. 测试对象适配器
在 Program.cs 中编写测试代码,验证 RobotObjectAdapter 的功能。
cs
class Program
{
static void Main(string[] args)
{
// 创建Robot实例
Robot robot = new Robot();
// 使用对象适配器将Robot适配为Player
IPlayer player = new RobotObjectAdapter(robot);
// 调用适配后的接口
player.Attack(); // 实际调用的是Robot的FireWeapon
player.Move(); // 实际调用的是Robot的Walk
}
}
4.4 运行结果
运行程序后,输出如下:
cs
机器人发射武器!
机器人行走!
4.5 理解对象适配器模式如何比类适配器模式更灵活
-
组合 vs 继承:
-
类适配器模式 :通过继承
Adaptee类实现适配,只能适配一个Adaptee类。 -
对象适配器模式 :通过组合
Adaptee类的实例实现适配,可以适配多个Adaptee类。
-
-
灵活性体现:
- 适配多个
Adaptee类 :对象适配器模式可以在运行时动态传入不同的Adaptee实例,适配多个类。例如:
- 适配多个
cs
IPlayer player1 = new RobotObjectAdapter(new Robot());
IPlayer player2 = new RobotObjectAdapter(new AdvancedRobot()); // 适配另一个Adaptee类
-
-
解耦 :对象适配器模式将适配器与
Adaptee类解耦,Adaptee类的变化不会影响适配器的实现。 -
符合设计原则:对象适配器模式遵循"组合优于继承"的原则,提高了代码的灵活性和可维护性。
-
-
类适配器模式的局限性:
-
类适配器模式通过继承实现,只能适配一个
Adaptee类。 -
继承关系可能导致类层次复杂,难以扩展。
-
-
对象适配器模式的优势:
-
更灵活:可以适配多个
Adaptee类,动态切换适配对象。 -
更易扩展:新增
Adaptee类时,无需修改适配器代码。 -
更符合面向对象设计原则:组合优于继承,降低了耦合度。
-
4.6 总结
-
实现对象适配器 :通过组合
Robot类的实例并实现IPlayer接口,将Attack()适配为FireWeapon(),将Move()适配为Walk()。 -
组合的作用 :组合使得
RobotObjectAdapter可以动态适配不同的Adaptee类,提高了灵活性。 -
运行结果 :程序输出
机器人发射武器!和机器人行走!,证明适配器模式功能实现。 -
对象适配器的灵活性 :对象适配器模式比类适配器模式更灵活,支持适配多个
Adaptee类,符合"组合优于继承"的设计原则。
5. 类适配器和对象适配器的应用场景与优缺点对比
| 对比项 | 类适配器模式 | 对象适配器模式 |
|---|---|---|
| 实现方式 | 通过继承 Adaptee 类实现适配。 |
通过组合 Adaptee 类的实例实现适配。 |
| 优点 | 1. 代码简洁,直接复用 Adaptee 的方法。 2. 无需额外创建 Adaptee 实例。 |
1. 更灵活,可以适配多个 Adaptee 类。 2. 符合组合优于继承的原则。 |
| 缺点 | 1. 只能适配一个 Adaptee 类。 2. 继承关系可能导致类层次复杂。 |
1. 需要额外创建 Adaptee 实例。 2. 代码量稍多。 |
| 适用场景 | 1. Adaptee 类的方法可以直接复用。 2. 不需要适配多个 Adaptee 类。 |
1. 需要适配多个 Adaptee 类。 2. 需要更灵活的适配方式。 |
总结
-
类适配器模式 适合在
Adaptee类的方法可以直接复用且不需要适配多个Adaptee类的场景,代码简洁但灵活性较低。 -
对象适配器模式 更适合需要适配多个
Adaptee类或需要更灵活适配方式的场景,虽然代码量稍多,但扩展性和灵活性更高。
