适配器模式(类适配器,对象适配器)
1. 适配器模式简介
适配器模式用于解决接口不兼容问题,将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。适配器模式主要分为两类:
类适配器模式:通过继承适配者类来实现适配。
对象适配器模式:通过组合适配者对象来实现适配。
2. 定义接口
在 Interfaces 文件夹中定义目标接口(Target)和需要适配的接口(Adaptee):
// IPlayer.cs - Target接口
public interface IPlayer
{
void Attack(); // 玩家角色攻击
void Move(); // 玩家角色移动
}
// 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
接口控制角色行为:
IPlayer player = new Player(); // 假设Player是IPlayer的实现类
player.Attack(); // 期望执行攻击
player.Move(); // 期望执行移动
但如果我们直接使用 Robot
类:
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
接口。
// 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
的功能。
class Program
{
static void Main(string[] args)
{
// 使用类适配器将Robot适配为Player
IPlayer player = new RobotAdapter();
// 调用适配后的接口
player.Attack(); // 实际调用的是Robot的FireWeapon
player.Move(); // 实际调用的是Robot的Walk
}
}
4. 运行结果
运行程序后,输出如下:
机器人发射武器!
机器人行走!
理解继承的方式如何让适配器类直接复用 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
接口。
// 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
的功能。
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 运行结果
运行程序后,输出如下:
机器人发射武器!
机器人行走!
4.5 理解对象适配器模式如何比类适配器模式更灵活
-
组合 vs 继承:
-
类适配器模式:通过继承
Adaptee
类实现适配,只能适配一个Adaptee
类。 -
对象适配器模式:通过组合
Adaptee
类的实例实现适配,可以适配多个Adaptee
类。
-
-
灵活性体现:
-
适配多个
Adaptee
类:对象适配器模式可以在运行时动态传入不同的Adaptee
实例,适配多个类。例如:
-
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
类或需要更灵活适配方式的场景,虽然代码量稍多,但扩展性和灵活性更高。