通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时候,现有的类可以满足客户类功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
定义:适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,
其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
结构:
- Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
- Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
- Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
适配器有两种,一种是类适配器,一种是对象适配器。
对象适配器结构图如下:
类适配器结构图如下:
实例解析:比如一家公司,有一套员工管理系统,负责统计公司员工的基本信息,现在由于业务扩展,找一个外包公司帮忙开展部分业务,而且需要将外包参与的人员信息录入到本公司信息系统中,不过外包公司也有自己的一套管理系统,跟公司的员工管理类完全不一样。 UML图如下:
代码分析:
public interface IMyEmployeeInfo {
// 获取员工姓名
public String getName();
// 获取员工年龄
public int getAge();
// 获取员工所在部门
public Object getDepartment();
// 获取员工电话
public String getPhoneNumber();
}
// 获取员工姓名
public String getName();
// 获取员工年龄
public int getAge();
// 获取员工所在部门
public Object getDepartment();
// 获取员工电话
public String getPhoneNumber();
}
public class MyEmployeeInfo
implements IMyEmployeeInfo{
private final String TAG = "test";
private String name = " 小张 ";
private int age = 20;
private String department = " 研发部 ";
private String number = "123456";
@Override
public String getName() {
Log. d( TAG, " 姓名: "+ name);
return name;
}
@Override
public int getAge() {
Log. d( TAG, " 年龄: "+ age);
return age;
}
@Override
public String getDepartment() {
Log. d( TAG, " 所在部门: "+ department);
return department;
}
@Override
public String getPhoneNumber() {
Log. d( TAG, " 电话: "+ number);
return number;
}
}
private final String TAG = "test";
private String name = " 小张 ";
private int age = 20;
private String department = " 研发部 ";
private String number = "123456";
@Override
public String getName() {
Log. d( TAG, " 姓名: "+ name);
return name;
}
@Override
public int getAge() {
Log. d( TAG, " 年龄: "+ age);
return age;
}
@Override
public String getDepartment() {
Log. d( TAG, " 所在部门: "+ department);
return department;
}
@Override
public String getPhoneNumber() {
Log. d( TAG, " 电话: "+ number);
return number;
}
}
public interface IOuterInfo {
// 获取员工基本信息
public Map getBaseInfo();
// 获取员工部门信息
public Map getOfficeInfo();
}
// 获取员工基本信息
public Map getBaseInfo();
// 获取员工部门信息
public Map getOfficeInfo();
}
public class OuterEmployeeInfo
implements IOuterInfo {
@Override
public Map getBaseInfo() {
HashMap baseInfoMap = new HashMap();
baseInfoMap.put( "name", " 小李 ");
baseInfoMap.put( "age", "35");
baseInfoMap.put( "number", "88888888");
return baseInfoMap;
}
@Override
public Map getOfficeInfo() {
HashMap officeInfoMap = new HashMap();
officeInfoMap.put( "office", " 代码组 ");
return officeInfoMap;
}
}
@Override
public Map getBaseInfo() {
HashMap baseInfoMap = new HashMap();
baseInfoMap.put( "name", " 小李 ");
baseInfoMap.put( "age", "35");
baseInfoMap.put( "number", "88888888");
return baseInfoMap;
}
@Override
public Map getOfficeInfo() {
HashMap officeInfoMap = new HashMap();
officeInfoMap.put( "office", " 代码组 ");
return officeInfoMap;
}
}
public class EmployeeInfoAdapter
extends OuterEmployeeInfo
implements IMyEmployeeInfo {
private final String TAG = "adapter";
private Map baseInfo = super.getBaseInfo();
private Map officeInfo = super.getOfficeInfo();
@Override
public String getName() {
String name = (String) this. baseInfo.get( "name");
Log. d( TAG, "OuterName: "+name);
return name;
}
@Override
public int getAge() {
int age = ( int) this. baseInfo.get( "age");
Log. d( TAG, "OuterAge: "+age);
return age;
}
@Override
public String getDepartment() {
String office = (String) this. officeInfo.get( "office");
Log. d( TAG, "OuterOffice: "+office);
return office;
}
@Override
public String getPhoneNumber() {
String number = (String) this. baseInfo.get( "number");
Log. d( TAG, "OuterNumber: "+number);
return number;
}
}
private final String TAG = "adapter";
private Map baseInfo = super.getBaseInfo();
private Map officeInfo = super.getOfficeInfo();
@Override
public String getName() {
String name = (String) this. baseInfo.get( "name");
Log. d( TAG, "OuterName: "+name);
return name;
}
@Override
public int getAge() {
int age = ( int) this. baseInfo.get( "age");
Log. d( TAG, "OuterAge: "+age);
return age;
}
@Override
public String getDepartment() {
String office = (String) this. officeInfo.get( "office");
Log. d( TAG, "OuterOffice: "+office);
return office;
}
@Override
public String getPhoneNumber() {
String number = (String) this. baseInfo.get( "number");
Log. d( TAG, "OuterNumber: "+number);
return number;
}
}
//
没有适配前,查看本公司员工信息
IMyEmployeeInfo myEmployeeInfo = new MyEmployeeInfo();
String name = myEmployeeInfo.getName();
// 用适配器模式后,查看的是外包公司员工信息了,相对适配前只修改了一行代码
IMyEmployeeInfo employeeInfoAdapter = new EmployeeInfoAdapter();
String outerName = employeeInfoAdapter.getName();
IMyEmployeeInfo myEmployeeInfo = new MyEmployeeInfo();
String name = myEmployeeInfo.getName();
// 用适配器模式后,查看的是外包公司员工信息了,相对适配前只修改了一行代码
IMyEmployeeInfo employeeInfoAdapter = new EmployeeInfoAdapter();
String outerName = employeeInfoAdapter.getName();
以上是类适配器的实例,如果是对象适配器,则适配器类(adapter)继承的适配者类(adaptee)改成在适配器类去实例化适配者类,拿到该对象,再去调用适配者类的方法获取相关的信息就行了。
优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
类适配器模式还具有如下优点:
- 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还具有如下优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点:
- 类适配器模式的缺点如下:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性, 不能将一个适配者类和它的子类都适配到目标接口。
- 对象适配器模式的缺点如下:
与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类, 将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适用环境:
- 系统需要使用现有的类,而这些类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
扩展:
认适配器模式(Default Adapter Pattern)或缺省适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式。