适配器模式

类结构型模式 / 对象结构型模式

对象适配器

类适配器

模式动机

首先举一个生活中适配器的例子:电源适配器

软件开发中采用类似于电源适配器设计思路的设计模式叫做适配器模式。

通常客户端可以通过已有类的接口访问它所提供的服务,满足客户类的功能需求,但是已有类所提供的接口不一定是客户类期望的,尽管它提供的服务是有用的,可能是因为现有类中方法名和期望的方法名不一致。

这种情况下,要想重用现有类以直接利用其提供的功能必须将现有接口转化为客户类期望的接口,适配器模式应运而生。

应用

在适配器模式中,定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器,它所包装的对象就是适配者,即被适配的类。

适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。即:当客户类调用适配器的方法时,在适配器类内部将调用适配者类的方法,这个过程对于客户类是透明的,客户类并不能访问适配者,只能访问适配器。如此,适配器就可以使由于接口不兼容而不能交互的类一起工作。

适配器模式包含如下角色:

  • 目标抽象类Target
  • 适配器类Adapter
  • 适配者类Adaptee
  • 客户类Client

典型类适配器代码如下:

public class Adapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}

典型对象适配器代码如下:

public class Adapter extends Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee;
}
public void request() {
adaptee.specificRequest();
}
}

实例:仿生机器人

现需要设计一个可以模拟各种动物行为的机器人,在机器人中定义了一系列方法,如机器人叫喊方法cry()、机器人移动方法move()等。如果希望在不修改已有代码的基础上使得机器人能够像狗一样叫,像狗一样跑,可以使用适配器模式进行系统设计。

实例:加密适配器

某系统需要提供一个加密模块,将用户信息(如密码等机密信息)加密之后再存储在数据库中,系统已经定义好了数据库操作类。为了提高开发效率,现需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码。使用适配器模式设计该加密模块,实现在不修改现有类的基础上重用第三方加密方法。

优点

  1. 解耦目标类和适配者类,引入适配器来重用现有的适配者类,不需要修改原来的代码
  2. 增加了类的透明性和复用性,具体的实现都封装在了适配者类中,客户端是看不见的(透明的),方便复用适配者也避免产生具体耦合
  3. 灵活性和可扩展性都很高,可以利用配置文件切换适配器,也可以在不修改已有的具体适配器的基础上添加新的适配器类,符合开闭原则
  4. (类适配器模式)并且由于适配器类是适配者类的子类,因此适配器类中可以自己按需重写一些方法,更加灵活
  5. (对象适配器模式)一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是同一个适配器可以把适配者类和它的子类都适配到目标接口(因为没有使用继承)

缺点

类适配器模式缺点

对于不支持多继承的语言(如Java、C#等),一次最多只能适配一个适配者,且目标抽象类只能是抽象类不能是具体类(因为不允许多继承所以只能是抽象类),使用上有局限性(比如不能直接实例化),不能将一个适配者类和它的子类都适配到目标接口上。(这句话的意思是,如果适配者有子类比如SubAdaptee,不能把这个子类和父类一起给适配,必须单独给每个子类写一个适配器,因为不支持多继承)

对象适配器模式缺点

与类适配取模式相比,置换适配者类的方法不容易,因为没有继承,想置换就必须先手动做一个适配者类的子类,子类重写,再适配子类,比较麻烦。

适用场合

  • 系统需要使用现有类,且这些类的接口不符合系统的需要
  • 想要建立一个可以重复使用的类,用于和一些彼此之间没有太大关联的类、可能在未来引进的类一起工作

模式扩展

默认适配器模式 / 缺省适配器模式

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式。

示例:

双向适配器

在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用适配者可以通过目标类的引用调用目标类中的方法,目标类也可以通过适配者的引用调用适配者类中的方法,那么该适配器就是一个双向适配器。

(•‿•)