1、定义
适配器模式(Proxy Pattern):将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
2、形式
通用类图如下所示:
其中,
Target
为目标角色,是转换的目标接口Adaptee
为源角色,是将被转换成目标角色的类或对象Adapter
为适配器角色,定义了将源角色转换为目标角色的方式,一般通过继承或是类关联。
对应代码如下:
Target
public interface Target {
public void request();
}
ConcreteTarget
public class ConcreteTarget implements Target{
public void request() {
System.out.println("I'm lee, are you ok?");
}
}
Adaptee
public class Adaptee {
public void doSomething(){
System.out.println("let me alone.");
}
}
Adapter
public class Adapter extends Adaptee implements Target{
public void request() {
super.doSomething();
}
}
Client
public class Client {
public static void main(String[] args) {
//原业务逻辑
Target target = new ConcreteTarget();
target.request();
//增加了适配器后
Target target2 = new Adapter();
target2.request();
}
}
示例:
假如当前设计了一个用户管理系统,用户信息类实现如下:
IUserInfo
public interface IUserInfo {
String getUserName();
String getHomeAddress();
String getMobileNumber();
String getHomeTelNumber();
/*set部分省略*/
}
UserInfo
public class UserInfo implements IUserInfo{
public String getUserName() {
System.out.println("姓名是..");
return null;
}
public String getHomeAddress() {
System.out.println("家庭住址是..");
return null;
}
public String getMobileNumber() {
System.out.println("电话号码是..");
return null;
}
public String getHomeTelNumber() {
System.out.println("家庭座机号码是..");
return null;
}
/*...*/
}
现在来了一个业务需求,需要与另一个系统进行对接,获取其相应的员工数据,对接系统实现员工信息管理方式为:
IOuterUser
public interface IOuterUser {
Map getUserBaseInfo();
Map getUserHomeInfo();
}
OuterUser
public class OuterUser implements IOuterUser{
public Map getUserBaseInfo() {
HashMap baseInfoMap = new HashMap();
baseInfoMap.put("username", "员工叫jack");
baseInfoMap.put("mobileNumber", "员工电话是12132...");
return baseInfoMap;
}
public Map getUserHomeInfo() {
HashMap homeInfoMap = new HashMap();
homeInfoMap.put("homeAddress", "家庭住址是universe...");
homeInfoMap.put("homeTelNumber", "家庭电话是8888...");
return homeInfoMap;
}
}
为了能够进行系统交互,采用适配器模式进行数据对象的转换,增加一个适配类
OuterUserInfo
public class OuterUserInfo extends OuterUser implements IUserInfo {
private Map baseInfo = super.getUserBaseInfo();
private Map homeInfo = super.getUserHomeInfo();
public String getUserName() {
String username = (String) baseInfo.get("username");
System.out.println(username);
return username;
}
public String getHomeAddress() {
String homeAddress = (String) homeInfo.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
}
public String getMobileNumber() {
String mobileNumber = (String) baseInfo.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
}
public String getHomeTelNumber() {
String homeTelNumber = (String) homeInfo.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
}
}
不与外部系统进行交互时,场景类代码如下
public class Client {
public static void main(String[] args) {
//原业务逻辑
IUserInfo staffs = new UserInfo();
/*
* 获取员工信息
* */
}
}
需要访问外部数据,与外部系统通过适配器进行交互
public class Client {
public static void main(String[] args) {
//原业务逻辑
IUserInfo staffs = new OuterUserInfo();
/*
* 获取员工信息
* */
}
}
可以看出,使用适配器模式后仅仅修改了一句代码就解决了系统对接的问题。
3、优缺点
优点
- 适配器模式可以让两个没有任何关系的类一起运行
- 增加了类的透明性,访问针对Target目标角色,具体实现对高层模块透明
- 增加了类的复用度,源角色在原有系统中可以正常使用,在目标角色中也可以通过适配进行访问。
- 灵活性好,适配器的删除对其他代码无影响
注:
- 适配器模式一般用于扩展应用,是为了解决正在服役的项目问题
- 项目一定要遵守依赖倒置原则和里氏替换原则,否则使用适配器模式需要对代码进行较大的修改。(这里比较好理解,目标角色是基于接口的,这样(满足依赖倒置原则)才能让高层模块跟底层实现解耦;使用适配器模式要继承或关联源角色,并实现目标角色,如果不满足里氏替换原则适配器将会改变一些代码行为(适配器替换源角色会改变代码行为))。
4、使用场景
当需要修改一个投产中的接口时,往往会用到适配器模式。比如系统扩展后一个类不符合原有的接口,因此需要用适配器模式进行修改。
5、扩展
第二小节中展示的适配器适用于单一接口和类的情况,当类和接口数有多个时,由于java不支持多继承,所以无法使用上述形式。
IOuterUerHomeInfo
public interface IOuterUerHomeInfo {
Map getUserHomeInfo();
}
IOuterUserBaseInfo
public interface IOuterUserBaseInfo {
Map getUserBaseInfo();
}
OuterUserBaseInfo
public class OuterUserBaseInfo implements IOuterUserBaseInfo{
public Map getUserBaseInfo() {
HashMap baseInfoMap = new HashMap();
baseInfoMap.put("username", "员工叫jack");
baseInfoMap.put("mobileNumber", "员工电话是12132...");
return baseInfoMap;
}
}
OuterUserHomeInfo
public class OuterUserHomeInfo implements IOuterUerHomeInfo{
public Map getUserHomeInfo() {
HashMap homeInfoMap = new HashMap();
homeInfoMap.put("homeAddress", "家庭住址是universe...");
homeInfoMap.put("homeTelNumber", "家庭电话是8888...");
return homeInfoMap;
}
}
因此,在这种情况下,我们进行扩展,使用类关联的方式来实现适配器。代码如下:
OuterUserInfo
public class OuterUserInfo extends OuterUser implements IUserInfo {
private IOuterUserBaseInfo baseInfo = null;
private IOuterUerHomeInfo homeInfo = null;
private Map baseMap = null;
private Map homeMap = null;
public OuterUserInfo(IOuterUserBaseInfo outerUserBaseInfo, IOuterUerHomeInfo outerUerHomeInfo){
this.baseInfo = outerUserBaseInfo;
this.homeInfo = outerUerHomeInfo;
this.baseMap = this.baseInfo.getUserBaseInfo();
this.homeMap = this.homeInfo.getUserHomeInfo();
}
public String getUserName() {
String username = (String) baseMap.get("username");
System.out.println(username);
return username;
}
public String getHomeAddress() {
String homeAddress = (String) homeMap.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
}
public String getMobileNumber() {
String mobileNumber = (String) baseMap.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
}
public String getHomeTelNumber() {
String homeTelNumber = (String) homeMap.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
}
}
场景类 Client
public class Client {
public static void main(String[] args) {
//原业务逻辑
IOuterUserBaseInfo outerUserBaseInfo = new OuterUserBaseInfo();
IOuterUerHomeInfo outerUerHomeInfo = new OuterUserHomeInfo();
IUserInfo staffs = new OuterUserInfo(outerUserBaseInfo, outerUerHomeInfo);
/*
* 获取员工信息
* */
}
}
类适配器和对象适配器
这种由类关联关系实现的适配器称之为对象适配器,之前的称之为类适配器。两者的主要区别就是在于是由类间继承还是由类间关联来实现适配器的构造,在实践中,对象适配器用的更多。
6、小结
在现实中,需求变更是无法逃避的,通过适配器模式可以以一个很小的代价来进行系统的补救。所以适配器模式本质上是一个补偿模式,常用于解决接口不相容的问题。