1. 定义
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
——《设计模式》GoF
- 适配,即在不改变原有实现的基础上,将原先不兼容的接口转换为兼容的接口。
- Adapter模式很像货物的包装过程:被包装的货物的真实样子被包装所掩盖和改变,因此有人把这种模式叫做包装(Wrapper)模式。事实上,大家经常写很多这样的Wrapper类,把已有的一些类包装起来,使之具有能满足需要的接口。
- 适配器模式有类适配器模式和对象适配器模式两种。
- 变化的是应用环境,从而要求不同的接口
- 这种模式很常见,比如力扣上的设计题,我们写了一个类来完成题目要求的功能,可能里面用到了数组或者栈结构,在客户端看来,我们完成了这个功能。这时候,我们写的类就是Adapter类,数组或者栈就是被实现包装的角色Adaptee
2. 结构
2.1 对象适配器(推荐使用)
客户端需要调用Request方法,而Adaptee没有该方法,为了使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例(对象),从而将客户端与Adaptee衔接起来。由于Adapter与Adaptee是组合关系,这决定了这个适配器模式是对象模式。
对象适配器的结构:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200721092523594.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMzQyMzI2,size_16,color_FFFFFF,t_70)
对象适配器的角色:
- 目标(Target)角色:这是客户所期待的接口。目标接口可以是抽象类,也可以是接口。
- 源(Adaptee)角色:需要被适配(实际完成工作)的类----源接口。
- 适配器(Adapter)角色:通过在内部包装(Wrap)一个Adaptee对象,把源接口转换成目标接口。
对象适配器在类似C#、Java这样的不支持多继承的语言中应用较多。
对象适配器示例
利用ArrayList实现栈结构–ObjectAdapterStack(看看源码)
namespace ObjectAdapterStack{
/// <summary>
/// Target角色--堆栈的抽象接口--给出堆栈类的主要操作Request
/// </summary>
interface IStack {
void Push(object item); //入栈
object Pop(); //出栈
object Top(); //获取栈顶元素,但不作出栈操作
}
}
namespace ObjectAdapterStack{
/// <summary>
/// Adapter--适配器,将Request转化为SpecificRequest
/// </summary>
class MyStack:IStack{
/// <summary>
/// Adaptee--动态数组,用于存储堆栈数据--被适配的对象
/// </summary>
private ArrayList list=null;
public MyStack()
{
list = new ArrayList();
}
/// <summary>
/// 进栈操作--Request
/// </summary>
/// <param name="obj">进栈的元素</param>
public void Push(object obj)
{
list.Add(obj); //将用户对栈的操作转换为对动态数组(Adaptee对象)的操作--SpecificRequest
}
/// <summary>
/// 出栈操作--Request
/// </summary>
/// <returns></returns>
public object Pop()
{
object obj = list[list.Count - 1]; //取出栈顶元素--SpecificRequest
list.RemoveAt(list.Count - 1); //删除栈顶元素--SpecificRequest
return obj;
}
/// <summary>
/// 取栈顶元素--Request
/// </summary>
/// <returns></returns>
public object Top()
{
return list[list.Count - 1]; //SpecificRequest
}
}
}
namespace ObjectAdapterStack{
class Program{
static void Main(string[] args){
//客户端创建Adapter对象,也可以从参数传入
IStack stack = new MyStack();
//通过Adapter对象调用实际的Adaptee对象完成具体的操作
stack.Push(1);
int i = (int)stack.Pop();
Console.WriteLine(i);
Console.ReadLine();
}
}
}
2.2 类适配器
Adaptee类没有Request方法,而客户期待这个方法。为了使客户能够使用Adaptee类,提供一个中间环节,即Adapter类,Adapter类实现了Target接口,并继承自Adaptee,Adapter类的Request方法重新封装了Adaptee的SpecificRequest方法,实现了适配的目的。
因为Adapter与Adaptee是继承的关系,所以这决定了这个适配器模式是类模式。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200721092543737.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMzQyMzI2,size_16,color_FFFFFF,t_70)
类适配器的角色:
- 目标(Target)角色:这是客户所期待的接口。因为C#不支持多继承,所以Target必须是接口,不可以是类。
- 源(Adaptee)角色:需要被适配的类。
- 适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类。
客户端程序应该使用Target访问适配器(避免胖接口)!!!
示例: ClassAdapterStack
类适配器主要应用于类似于C++的支持多继承的语言。
总的来说:对象适配器采用组合方式实现,而类适配器采用继承方式实现,按照优先使用组合的松耦合原则,应该优先考虑使用对象适配器。
3. 优缺点及适用环境
- Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
- 类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。
- 对象适配器采用“对象组合”的方式,更符合松耦合精神。
这个模式介绍的很简单,复杂的没讲