真刀实枪之适配器模式
- 上帝才能控制的事-业务发展
- “智者千虑必有一失,愚者千虑亦有一得”
- 系统开发时,不管之前的可行性分析、需求分析、系统设计处理的多么完美,总会在关键时候出一些意外,该面对的是躲不了的,那该怎们弥补呢?
- 从一个项目说起--这个项目是一个人力资源项目,分为三大模块:人员信息管理,薪酬管理,职位管理。当时的需求是这样的:
- 人员信息管理的对象是所有员工的信息
- 所有的员工指的是在职的员工,其他离职的、退休的暂不考虑
- 根据需求可以设计啦
- 类图
- 该类图还不太完善,还有许多子类--即业务对象--这个对象设计为贫血对象
- 类图
- 这里出来个新的名词--贫血对象,那先来解释一下
- 贫血对象(Thin Bussiness Object):不需要存储状态以及相关关系的类
- 充血对象(Rich Business Object ):有实体状态和对象关系的类
-
代码
-
IUserInfo
package com.peng.spq; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public interface IUserInfo { // 获得用户姓名 public String getUserName(); // 获得家庭地址 public String getHomeAddress(); // 获得手机号码 public String getMobileNumber(); // 获得办公电话 public String getOfficeTelNumber(); // 获得职位 public String getJobPosition(); // 获得家庭电话 public String getHomeTelNumber(); }
-
UserInfo
package com.peng.spq; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class UserInfo implements IUserInfo { @Override public String getUserName() { System.out.println("这是员工的名字"); return null; } @Override public String getHomeAddress() { System.out.println("这是员工的家庭住址"); return null; } @Override public String getMobileNumber() { System.out.println("这是员工的的手机号"); return null; } @Override public String getOfficeTelNumber() { System.out.println("这是员工的办公室电话"); return null; } @Override public String getJobPosition() { System.out.println("这是员工的职位"); return null; } @Override public String getHomeTelNumber() { System.out.println("这是员工的家庭电话"); return null; } }
-
- 过了段时间,很多公司开始开始使用借聘人员的方式引进人员--这时要增加一个功能【借用人员管理】
- 分析下这个新加的功能:借聘人员虽然在我们公司干活,和我们一样,干活没有什么差别,但是他们的人员信息、工资情况、福利情况都是由劳务服务公司管理的,并且有自己的一套的人员管理系统,人力资源部门就要求我们系统同步劳动服务公司的信息,其他人员的信息不需要,而且需要信息的同步性--也就是劳动服务公司的人员信息一改变,这边就要立即显示出来
-
有了任务,那开始解决呗,先看看RMI(Remote Method Invocation,远程对象调用),进行联机交互,但是深入分析之后,有一个重大问题:劳动服务公司的对象和我们的对象不相同
-
劳动服务公司的类图
-
代码
-
IOuterUser
package com.peng.spq; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public interface IOuterUser { // 获取基本信息 public Map getUserBaseInfo(); // 获取工作信息 public Map getUserOfficeInfo(); // 获取家庭信息 public Map getUserHomeInfo(); }
-
OuterInfo
package com.peng.spq; import java.util.HashMap; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class OuterUser implements IOuterUser { @Override public Map getUserBaseInfo() { // 用户基本信息 HashMap baseInfoMap = new HashMap(); baseInfoMap.put("username", "kungfu~peng"); baseInfoMap.put("sex", "男"); baseInfoMap.put("age", "24"); return baseInfoMap; } @Override public Map getUserOfficeInfo() { // 办公信息 HashMap offericeMap = new HashMap(); offericeMap.put("job", "Designer"); offericeMap.put("offerNumber", "1104561822"); return offericeMap; } @Override public Map getUserHomeInfo() { // 家庭信息 HashMap homeMap = new HashMap(); homeMap.put("address", "ShanXi"); homeMap.put("homeNumber", "15034031282"); return homeMap; } }
-
-
-
既然有了数据,那就将转换一下,增加一层转换处理,设计类图如下:
-
大家可能有这样的疑问:两个对象不在一个系统,那该怎么交流呢,RMI已经帮我们做了这件事,只要有接口,就看我一把远程的对象当成本地的对象使用;OuterUserInfo可以看成是两面派,实现了IUserInfo接口,还继承了OuterUser,通过这样的设计,可以把OuterUser伪装成我们系统中的一个IUserInfo对象
-
IUserInfo
package com.peng.spq; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description 中转站 */ public class OuterUserInfo extends OuterUser implements IUserInfo { // 用户基本信息 Map baseInfoMap = super.getUserBaseInfo(); // 办公信息 Map offericeMap = super.getUserOfficeInfo(); // 家庭信息 Map homeMap = super.getUserHomeInfo(); @Override public String getUserName() { String userName = (String) this.baseInfoMap.get("username"); System.out.println(userName); return userName; } @Override public String getHomeAddress() { String homeAddress = (String) this.homeMap.get("address"); System.out.println(homeAddress); return homeAddress; } @Override public String getMobileNumber() { String mobileNumber = (String) this.baseInfoMap.get("mobileNumber"); System.out.println(mobileNumber); return mobileNumber; } @Override public String getOfficeTelNumber() { String officeTelNumber = (String) this.offericeMap.get("offerNumber"); System.out.println(officeTelNumber); return officeTelNumber; } @Override public String getJobPosition() { String jodPosition = (String) this.offericeMap.get("job"); System.out.println(jodPosition); return jodPosition; } @Override public String getHomeTelNumber() { String homeTelNumber = (String) this.homeMap.get("homeNumber"); System.out.println(homeTelNumber); return homeTelNumber; } }
-
Client
package com.peng.spq; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class Client { public static void main(String[] args) { // 没有有外系统相连的时候 IUserInfo boy = new UserInfo(); boy.getMobileNumber(); boy.getHomeAddress(); boy.getHomeTelNumber(); boy.getJobPosition(); boy.getOfficeTelNumber(); boy.getUserName(); // 查外系统 IUserInfo boy1 = new OuterUserInfo(); boy1.getMobileNumber(); boy1.getHomeAddress(); boy1.getHomeTelNumber(); boy1.getJobPosition(); boy1.getOfficeTelNumber(); boy1.getUserName(); } }
-
结果
这是员工的的手机号 这是员工的家庭住址 这是员工的家庭电话 这是员工的职位 这是员工的办公室电话 这是员工的名字 15034031282 ShanXi 15034031282 Designer 1104561822 kungfu~peng
-
- 大家看,使用了适配器模式只修改了一句话,就可以将两个系统联系起来,其他业务逻辑都不用修改就解决了系统的对接问题,而在我们的实际系统中只增加了一个业务类的继承,就实现了可以查询本公司的员工信息和人力资源公司的员工信息,尽量少的修改,通过扩展的方式解决了该问题,这就是适配器模式。
-
-
适配器定义
- Adapter Pattern
- Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作)
- 适配器也叫变压器模式,也叫做包装模式(Wapper),但包装模式不止一个,还有装饰模式。
- 通用类图
- 适配器在生活中很常见,比如你现在看的电脑【笔记本电脑】的电源适配器,在110~220V范围内都可以正常使用。简单说,适配器模式就是把一个接口或一个类装换成其他接口或类,看看原始的适配器图
- 适配器的三个角色
- Target目标角色:期望接口
- Adaptee源角色:被转化的接口
- Adapter适配器角色:将源角色装换为目标角色
-
通用源码
-
Target
package spq2; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public interface Target { // 目标角色自己的方法 public void request(); }
-
ConcreteTarget
package spq2; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class ConcreteTarget implements Target { @Override public void request() { System.out.println("if you need any help.please call me!"); } }
-
Adaptee
package spq2; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class Adaptee { // 原有的业务逻辑 public void doSomething() { System.out.println("I'm kind of busy.leave me alone.please!"); } }
-
Adapter
package spq2; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class Adapter extends Adaptee implements Target { @Override public void request() { super.doSomething(); } }
-
Client
package spq2; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class Client { public static void main(String[] args) { // 原有的业务逻辑 Target target = new ConcreteTarget(); target.request(); // 加了适配器的业务逻辑 Target t2 = new Adapter(); t2.request(); } }
-
适配器模式的应用
- 适配器模式的优点
- 适配器模式可以使两个没有任何关系的类在一起运行
- 增加了类的透明性--高层不需要关心
- 提高了类的复用度
- 灵活性非常好
- 适配器模式的使用场景
- 有动机修改一个已经投产中的接口
- 系统扩展--需要使用一个已有的或建立的类,但这个类又不符合系统的接口
- 适配器模式的注意事项
- 设计时先别考虑它--它是解决服役时出现的问题【扩展应用】
- 一定要遵守依赖倒置和里式替换原则,否则即使适合适配器的场合也会带来非常大的改造。
适配器模式的扩展--对象适配器
- 类关联实现多关联--解决java不能多继承
- 上述公司中又增加了几个接口:除了用户基本信息,又增加了工作信息接口,家庭信息接口和工作信息接口
-
类图
-
-
IUserInfo
package spq3; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public interface IUserInfo { // 获得用户姓名 public String getUserName(); // 获得家庭地址 public String getHomeAddress(); // 获得手机号码 public String getMobileNumber(); // 获得办公电话 public String getOfficeTelNumber(); // 获得职位 public String getJobPosition(); // 获得家庭电话 public String getHomeTelNumber(); }
-
UserInfo
package spq3; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class UserInfo implements IUserInfo { @Override public String getUserName() { System.out.println("这是员工的名字"); return null; } @Override public String getHomeAddress() { System.out.println("这是员工的家庭住址"); return null; } @Override public String getMobileNumber() { System.out.println("这是员工的的手机号"); return null; } @Override public String getOfficeTelNumber() { System.out.println("这是员工的办公室电话"); return null; } @Override public String getJobPosition() { System.out.println("这是员工的职位"); return null; } @Override public String getHomeTelNumber() { System.out.println("这是员工的家庭电话"); return null; } }
-
OuterUserInfo【对象适配器】
package spq3; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description 对象适配器 */ public class OuterUserInfo implements IUserInfo { // 源目标对象 private IOuterUserBaseInfo baseInfo = null; private IOuterUserHomeInfo homeInfo = null; private IOuterUserOfficeInfo officeInfo = null; // 数据处理 private Map baseMap = null; private Map officeMap = null; private Map homeMap = null; // 构造函数 public OuterUserInfo(IOuterUserBaseInfo baseInfo, IOuterUserHomeInfo homeInfo, IOuterUserOfficeInfo officeInfo) { super(); this.baseInfo = baseInfo; this.homeInfo = homeInfo; this.officeInfo = officeInfo; this.baseMap = this.baseInfo.getUserBaseInfo(); this.homeMap = this.homeInfo.getUserHomeInfo(); this.officeMap = this.officeInfo.getUserOfficeInfo(); } @Override public String getUserName() { String userName = (String) this.baseMap.get("username"); System.out.println(userName); return userName; } @Override public String getHomeAddress() { String homeAddress = (String) this.homeMap.get("address"); System.out.println(homeAddress); return homeAddress; } @Override public String getMobileNumber() { String mobileNumber = (String) this.baseMap.get("mobileNumber"); System.out.println(mobileNumber); return mobileNumber; } @Override public String getOfficeTelNumber() { String officeTelNumber = (String) this.officeMap.get("offerNumber"); System.out.println(officeTelNumber); return officeTelNumber; } @Override public String getJobPosition() { String jodPosition = (String) this.officeMap.get("job"); System.out.println(jodPosition); return jodPosition; } @Override public String getHomeTelNumber() { String homeTelNumber = (String) this.homeMap.get("homeNumber"); System.out.println(homeTelNumber); return homeTelNumber; } }
-
IOuterUserBaseInfo
package spq3; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public interface IOuterUserBaseInfo { // 获取用户的基本信息 public Map getUserBaseInfo(); }
-
IOuterUserHomeInfo
package spq3; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public interface IOuterUserHomeInfo { // 获取用户的家庭信息 public Map getUserHomeInfo(); }
-
IOuterUserOfficeInfo
package spq3; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public interface IOuterUserOfficeInfo { // 获取用户的办公信息 public Map getUserOfficeInfo(); }
-
OuterUserBaseInfo
package spq3; import java.util.HashMap; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class OuterUserBaseInfo implements IOuterUserBaseInfo { // 用户基本信息 Map baseInfoMap = new HashMap(); @Override public Map getUserBaseInfo() { baseInfoMap.put("username", "kungfu~peng"); baseInfoMap.put("sex", "男"); baseInfoMap.put("age", "24"); baseInfoMap.put("mobileNumber", "15034031282"); return baseInfoMap; } }
-
OuterUserHomeInfo
package spq3; import java.util.HashMap; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class OuterUserHomeInfo implements IOuterUserHomeInfo { // 家庭信息 HashMap homeMap = new HashMap(); @Override public Map getUserHomeInfo() { homeMap.put("address", "ShanXi"); homeMap.put("homeNumber", "15034031282"); return homeMap; } }
-
OuterUserOfficeInfo
package spq3; import java.util.HashMap; import java.util.Map; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class OuterUserOfficeInfo implements IOuterUserOfficeInfo { // 办公信息 HashMap offericeMap = new HashMap(); @Override public Map getUserOfficeInfo() { offericeMap.put("job", "Designer"); offericeMap.put("offerNumber", "1104561822"); return offericeMap; } }
-
Client
package spq3; /** * @author kungfu~peng * @data 2017年11月23日 * @description */ public class Client { public static void main(String[] args) { // 外系统人员信息 IOuterUserBaseInfo baseInfo = new OuterUserBaseInfo(); IOuterUserHomeInfo homeInfo = new OuterUserHomeInfo(); IOuterUserOfficeInfo officeInfo = new OuterUserOfficeInfo(); // 创建对象适配器 IUserInfo boy = new OuterUserInfo(baseInfo, homeInfo, officeInfo); // 获取数据 boy.getHomeAddress(); boy.getHomeTelNumber(); boy.getJobPosition(); boy.getMobileNumber(); boy.getOfficeTelNumber(); boy.getUserName(); } }
-
结果
ShanXi 15034031282 Designer 15034031282 1104561822 kungfu~peng
-
-
这个扩展的模式叫做对象适配器
- 对象适配器的通用类图
- 对象适配器的通用类图
-
对象适配器了类适配器的区别:
- 类适配器是类间继承,对象适配器是对象的合成关系--根本区别
- 类适配器:只能通过覆写源角色来进行扩展
- 对象适配器:灵活,修补源角色的隐形缺陷,关联其他对象
最佳实践
- 世上没有“十全十美”---适配器做补偿----补偿模式、补救模式
- 技术是为业务服务的,在业务日新月异的变化中,这些补救模式能够保证我们的系统在生命周期内能够稳定、可靠、健壮的运行,适配器就是这样的一个“救世主”
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正。