1 | 适配器模式概述
我们联想一下常见的生活现象,我们国家的生活用电的电压是 220V,而手机,笔记本,电视机等的设备的实际工作电压没有这么高,为了使这些设备可以使用 220V 的生活用电,需要电源适配器(AC Adpater),也就是充电器或变压器,有了这个电源适配器,原本不能直接工作的生活电器等设备就可以兼容使用了,而这其中的电源适配器就充当了一个适配器的角色。
同样的在软件开发中,有时也会出现这种类似的不兼容的情况,我们也可以像引入一个电源适配器那样引入一个称之为适配器的角色来协调这些相互之间存在不兼容的结构,这种设计方案即为适配器模式。
与电源适配器相似,在适配器模式中引人了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
1.1 适配器模式的定义
- 适配器模式:将一个类的接 口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
- Adapter Pattern: Convert the interface of a class into another interface clients expect. Adapter lets casses work together that could't otherwise because of icompaible nteraces.
适配器模式可以将一个类的接口和另外一个类的接口匹配起来,而无需修改原来的适配者接口和抽象目标接口类。
适配器模式的别名为包装器(Wrapper)模式,它既可以作为类结构型模式,也可以作为对象结构型模式。在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。
2 | 适配器模式的结构与实现
适配器模式包括类适配器和对象适配器。在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。下面分别对两种适配器进行结构分析。
2.1 适配器模式的结构
适配器模式包含以下 3 个角色
- (1) Target(目标抽象类):目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于 C# 语言特性单一继承和多接口扩展。
- (2) Adapter(适配器类):它可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配。适配器 Adapter 是适配器模式的核心,在类适配器中,它通过实现 Target 接口并继承 Adaptee 类来使二者产生联系,在对象适配器中,它通过继承 Target 并关联一个 Adaptee 对象是二者产生联系。
- (3) Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,句含了客户希望使用的业务方法,在某些情况下甚至没有适配者类的源代码。
2.2 适配器模式的实现
由于适配器模式包括类适配器模式和对象适配器模式两种形式,下面分别介绍这两种适配器模式的实现机制。
2.2.1 类适配器
在类适配器中,适配者类 Adaptee 没有 Beavast() 方法,而客户期待这个方法,但在适配者类中实现了SpecificRequest() 方法,该方法所提供的实现正是客户所需要的。为了使客户能够使用适配者类,提供了一个中间类,即适配磊类Adaptet ,适配磊类实现了物象目标类接口 Target ,并继承了适配者类,在适配器类的 Reqpest() 方法中调用所继承的适配者类的 SpecificRequest() 方法,达到了适配的目的,因为适配器类与适配者类是继承关系,所以这种适配器模式称为类适配器模式。典型的类适配器代码如下:
interface ITarget
{
string Request();
}
class Adaptee
{
public string SpecificRequest() => "Adaptee.SpecificRequest";
}
class ClassAdapter : Adaptee, ITarget
{
public string Request() => $"ClassAdapter.Request 调用 {base.SpecificRequest()}";
}
2.2.2 对象适配器
在对象适配器中,客户端需要调用 Request() 方法,而适配者类 Adaptee 没有该方法但是它所提供的 SpecificRequest() 方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类 Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的 Request() 方法中调用适配者的 SpecificRequest() 方法。因为适配器类与适配者类是关联关系(也可称为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下:
class ObjectAdapter : ITarget
{
private readonly Adaptee _Adaptee; //维持一个适配者对象的引用
public ObjectAdapter(Adaptee adaptee)
{
_Adaptee = adaptee;
}
public string Request() => $"ObjectAdapter.Request 调用 {_Adaptee.SpecificRequest()}";
}
适配器模式可以将一个类的接口和另一个类的接口匹配起来,使用的前提是不能或不想修改原来的适配者接口和抽象目标类接口。例如:购买了一些第三方类库或控件,但是没有源代码,此时使用适配器模式可以统一对象访问接口。
适配器模式更多的是强调对代码的组织,而不是功能的实现。在实际开发中,对象适配器的使用频率更高。
客户端调用
3 | 适配器模式的应用实例
代码设计
- ScoreOperation :抽象成绩操作类,充当目标接口。
- QuickSortClass:快速排序类,充当适配者。
- BinarySearchClass:二分查找类,充当适配这类。
- 配置文件 App.config ,在配置文件中存储了适配器类的类名(全名)。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="adapter" value="AdapterPattern.Sample.OperationAdapter"/>
</appSettings>
</configuration>
客户端调用
完整代码示例请查看 =》https://gitee.com/dolayout/DesignPatternOfCSharp/tree/master/DesignPatternOfCSharp/AdapterPattern
4 | 省缺适配器模式
省缺适配器模式是适配器模式的一种变体,其应用也较为广发,该模式也称为单接口适配器模式。
4.1 省缺适配器模式定义
- 缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以有选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
4.2 缺省适配器模式中包含以下 3 个角色
- (1) IServiceInterface(适配者接口):它是一个接口,通常在该接口中声明了大量的方法。
- (2) AbstractServiceClass(缺省适配器类):它是缺省适配器模式的核心类,使用空方法的形式实现了在 ServiceInterface 接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义。
- (3) ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前,它需要实现适配者接口,因此需要实现在适配者接口中声明的所有方法,而对于一些无须使用的方法不得不提供空实现。在有了缺省适配器之后,可以直接继承该适配器类,根据需要有选择性地覆盖在话配器类中定义的方法。
其中,缺省适配器类的典型代码片段如下:
interface IServiceInterface
{
void ServiceMethod1();
void ServiceMethod2();
void ServiceMethod3();
}
abstract class AbstractServiceClass : IServiceInterface
{
public void ServiceMethod1() { } // 空方法
public void ServiceMethod2() { }
public void ServiceMethod3() { }
}
5 | 双向适配器模式
在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器。
双向适配器的实现较为复杂,其典型代码如下:
class TwoWayApapter : Adaptee, ITarget
{
// 同时维持抽象目标类和适配者的引用
private readonly Adaptee _Adaptee;
private readonly ITarget _Target;
public TwoWayApapter(Adaptee adaptee)
{
_Adaptee = adaptee;
}
public TwoWayApapter(ITarget target)
{
_Target = target;
}
public new string SpecificRequest() => _Target.Request();
public string Request() => _Adaptee.SpecificRequest();
}
6 | 适配器模式的优缺点与适用环境
适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得到了广泛的应
6.1 适配器模式的优点(无论是对象适配器模式还是类适配器模式都具有)
- (1)将目标类和适配者类解耦,通过引人一个适配器类来重用现有的适配者类,无须修改原有结构。
- (2)增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个是是配者类可以在多个不同的系统中复用。
- (3)灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则。
具体来说,类适配器模式还具有以下优点:
- 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使适配器的灵活性更强。
对象适配器模式还具有以下优点:
- (1)一个对象适配器可以把多个不同的适配者适配到同一个目标。
- (2)对象适配器模式可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据里氏代换原则,适配者的子类也可通过该适配器进行适配。
6.2 适配器模式的缺点
类适配器模式的主要缺点如下:
- (1)对于 C#、Java 等不支持多重类继承的语言,一次最多只能适配一个适配者类,能同时适配多个适配者。
- (2)适配者类不能为最终类,例如在 C# 中不能为 sealed 类。
- (3)在 C#、Java 等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
对象适配器模式的主要缺点如下:
- 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置掉然后再把该适配者类的子类当做真正的适配者进行适配,其实现过程较为复杂。
6.3 适配器模式的适用环境
- (1)系统需要使用一些现有的类,而这些类的接口(例如方法名)不符合系统的需要,甚至没有这些类的源代码。
- (2)创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作。