24种设计模式--适配器模式【Adapter Pattern】
今天讲适配器模式,这个模式也很简单,你笔记本上的那个拖在外面的黑盒子就是个适配器,一般你在中国能用,在日本也能用,虽然两个国家的的电源电压不同,中国是 220V,日本是 110V,但是这个适配器能够把这些不同的电压转换为你需要的 36V 电压,保证你的笔记本能够正常运行,那我们在设计模式中引入这个适配器模式是不是也是这个意思呢?是的,一样的作用,两个不同接口,有不同的实现,但是某一天突然上帝命令你把 B 接口转换为 A 接口,怎么办?继承,能解决,但是比较傻,而且还违背了 OCP 原则,怎么办?好在我们还有适配器模式。
适配器的通用类图是这个样子滴:
这个类图也很容易理解,Target 是一个类(接口),Adaptee 是一个接口,通过 Adapter 把 Adaptee 包装成 Target 的一个子类(实现类),注意了这里用了个名词包装(Wrapper),那其实这个模式也叫做包装模式(Wrapper),但是包装模式可不知一个,还包括了以后要讲的装饰模式。我来讲个自己的一个经验,让大家可以更容易了解这个适配器模式,否则纯讲技术太枯燥了,技术也可以有娱乐的嘛。我曾经做了一个人力资源管理,这个项目还是比较简单的,分为三大模块:人员信息管理,薪酬管理,职位管理,其中人员管理这块就用到了适配器模式,是怎么回事呢?当时开发时明确的指明:人员信息简管理的对象是所有员工的所有信息,然后我们就这样设计了一个类图:
还是比较简单的,有一个对象 UserInfo 存储用户的所有信息,也就是 BO(Business Object),这个对象设计为贫血对象(Thin Business Object),不需要存储状态以及相关的关系,而且我也是反对使用充血对象(Rich Business Object),这里说了两个名词贫血对象和充血对象,这两个名词很简单,在领域模型中分别叫做贫血领域模型和充血领域模型,有什么区别呢?在一个对象中不存储实体状态以及对象之间的关系的就叫做贫血对象,上升到领域模型中就是贫血领域模型,有实体状态和对象关系的模型的就是充血领域模型,是不是太技术化了?这个看不懂没关系,属于专用名词。这个 UserInfo 对象,在系统中很多地方使用,你可以查看自己的信息,也可以做修改,当然这个对象是有 setter 方法的,我们这里用不到就隐藏掉了。
这个项目还是比较平稳的,中间修修补补也很正常,不知道是那股风吹的,很多公司开始使用借聘人员的方式招聘人员,我们公司也不例外,从一个人力资源
公司借用了一大批的低技术、低工资的人员,分配到各个子公司,总共有将近 200 号人,然后就找我们部门老大谈判,说要增加一个功能借用人员管理,老大一看有钱赚呀,一拍大腿,做!我带人过去一调研,不是这么简单,人力资源公司有一套自己的人员管理系统,我们公司需要把我们使用到的人员信息传输到我们的系统中,系统之间的传输使用 RMI(Remote Method Invocation,远程对象调用)的方式,但是有一个问题人力资源公司的人员对象和我们系统的对象不相同呀,他们的对象是这样的:
人员资源公司是把人的信息分为了三部分:基本信息,办公信息和个人家庭信息,并且都放到了 HashMap中,比如人员的姓名放到 BaseInfo 信息中,家庭地址放到 HomeInfo 中,这咱不好说他们系统设计的不好,那问题是咱的系统要和他们系统有交互,怎么办?使用适配器模式,类图如下:
大家可能会问,这两个对象都不在一个系统中,你如何使用呢?简单!RMI 已经帮我们做了这件事情,只要有接口,就可以把远程的对象当成本地的对象使用,这个大家有时间可以去看一下 RMI 文档,不多说了。通过适配器,把 OuterUser 伪装成我们系统中一个 IUserInfo 对象,这样,我们的系统基本不用修改什么程序,所有的人员查询、调用跟本地一样样的,说的口干舌燥,那下边我们来看具体的代码实现:
首先看 IUserInfo.java 的代码:
package com.pattern.adapter; /**
* 用户信息对象
* @author http://www.cnblogs.com/initial-road/
*
*/
public interface IUserInfo { //获得用户姓名
String getUserName(); //获得家庭地址
String getHomeAddress(); //手机号码,这个太重要,手机泛滥呀
String getMobileNumber(); //办公电话,一般是座机
String getOfficeTelNumber(); //这个人的职位是啥
String getJobPosition(); //获得家庭电话
String getHomeTelNumber(); } 然后看这个接口的实现类:
package com.pattern.adapter; /**
* 用户实现类
* @author http://www.cnblogs.com/initial-road/
*
*/
public class UserInfo implements IUserInfo{ /**
* 获得家庭地址,下属送礼也可以找到地方
*/
@Override
public String getHomeAddress() {
System.out.println("这里是员工的家庭地址");
return "家庭地址";
} @Override
public String getHomeTelNumber() {
System.out.println("员工的家庭电话是...");
return "家庭电话";
} /**
* 获得家庭号码
*/
@Override
public String getJobPosition() {
System.out.println("这个人的职位是Boss...");
return "员工职位";
} /**
* 员工的职位,是部门经理还是小兵
*/
@Override
public String getMobileNumber() {
System.out.println("这个人的手机号码是0000...");
return "手机号码";
} @Override
public String getOfficeTelNumber() {
System.out.println("办公电话是...");
return "办公电话";
} @Override
public String getUserName() {
System.out.println("姓名叫做...");
return "员工姓名";
} }
可能有人要问了,为什么要把电话号码、手机号码都设置成 String 类型,而不是 int 类型,大家觉的呢?题外话,这个绝对应该是 String 类型,包括数据库也应该是 varchar 类型的,手机号码有小灵通带区号的,比如 02100001,这个你用数字怎么表示?有些人要在手机号码前加上 0086 后再保存,比如我们公司的印度阿三就是这样,喜欢在手机号码前 0086 保存下来,呵呵,我是想到啥就说啥,啰嗦了点。继续看我们的代码,下面看我们系统的应用如何调用 UserInfo 的信息:
package com.pattern.adapter; /**
* 具体的应用
* @author http://www.cnblogs.com/initial-road/
*
*/
public class App { public static void main(String[] args) {
//没有于外系统连接的时候,是这样写的
IUserInfo girl = new UserInfo();
//从数据库查到200个
for(int i=0;i<200;i++){
girl.getMobileNumber();
}
} }
从数据库中生成了 200 个 UserInfo 对象,直接打印出来就成了。那然后增加了外系统的人员信息,怎么处理呢?下面是 IOuterUser.java 的源代码:
package com.pattern.adapter; import java.util.Map; /**
* 外系统的人员信息
* @author http://www.cnblogs.com/initial-road/
*
*/
public interface IOuterUser { /**基本信息,比如名称,性别,手机号码等**/
Map getUserBaseInfo(); /**工作区域信息**/
Map getUserOfficeInfo(); /**用户的家庭信息**/
Map getUserHomeInfo(); }
我们再看看外系统的用户信息的具体实现类:
package com.pattern.adapter; import java.util.HashMap;
import java.util.Map; /**
* 外系统的用户信息的实现类
* @author http://www.cnblogs.com/initial-road/
*
*/
public class OuterUser implements IOuterUser{ @Override
public Map getUserBaseInfo() {
HashMap baseInfoMap = new HashMap();
baseInfoMap.put("userName", "这个员工叫混世魔王");
baseInfoMap.put("mobileNumber", "这个员工电话是...");
return baseInfoMap;
} @Override
public Map getUserHomeInfo() {
HashMap homeInfo = new HashMap();
homeInfo.put("homeTelNumber", "员工的家庭电话是...");
homeInfo.put("homeAddress", "员工的家庭地址是...");
return homeInfo;
} @Override
public Map getUserOfficeInfo() {
HashMap officeInfo = new HashMap();
officeInfo.put("jobPosition", "这个人的职位是Boss...");
officeInfo.put("officeTelNumber", "员工的办公电话是...");
return officeInfo;
} }
那怎么把外系统的用户信息包装成我们公司的人员信息呢?看下面的 OuterUserInfo 类源码,也就是我们的适配器:
package com.pattern.adapter; import java.util.Map; /**
* 把OuterUser包装成UserInfo
* @author http://www.cnblogs.com/initial-road/
*
*/
public class OuterUserInfo extends OuterUser implements IUserInfo{
/**员工的基本信息**/
private Map baseInfo = super.getUserBaseInfo();
/**员工的家庭信息**/
private Map homeInfo = super.getUserHomeInfo();
/**员工的工作信息**/
private Map officeInfo = super.getUserOfficeInfo(); @Override
public String getHomeAddress() {
String homeAddress = (String)this.homeInfo.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
} @Override
public String getHomeTelNumber() {
String homeTelNumber = (String)this.homeInfo.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
} @Override
public String getJobPosition() {
String jobPosition = (String)this.officeInfo.get("jsoPosistion");
System.out.println(jobPosition);
return jobPosition;
} @Override
public String getMobileNumber() {
String mobileNumber = (String)this.baseInfo.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
} @Override
public String getOfficeTelNumber() {
String officeTelNumber = (String)this.officeInfo.get("officeTelNumber");
System.out.println(officeTelNumber);
return officeTelNumber;
} @Override
public String getUserName() {
String userName = (String) this.baseInfo.get("userName");
System.out.println(userName);
return userName;
} }
大家看到没?这里有很多的强制类型转换,就是(String)这个东西,如果使用泛型的话,完全就可以避免这个转化。这个适配器的作用就是做接口的转换,那然后我们再来看看我们的业务是怎么调用的:
package com.pattern.adapter; /**
* 具体的应用
* @author http://www.cnblogs.com/initial-road/
*
*/
public class App { public static void main(String[] args) {
//没有于外系统连接的时候,是这样写的
IUserInfo girl = new UserInfo();
//从数据库查到200个
for(int i=0;i<200;i++){
girl.getMobileNumber();
} //使用外系统的资源,我们至修改这一句就好了
IUserInfo girlOne = new OuterUserInfo();
//从数据库中查到200个
for(int i=0;i<200;i++){
girlOne.getMobileNumber();
}
} }
大家看,使用了适配器模式只修改了一句话,其他的业务逻辑都不用修改就解决了系统对接的问题,而且在我们实际系统中只是增加了一个业务类的继承,就实现了可以查本公司的员工信息,也可以查人力资源公司的员工信息,尽量少的修改,通过扩展的方式解决了该问题。
适配器模式分为类适配器和对象适配器,这个区别不大,上边的例子就是类适配器,那对象适配器是什么样子呢?对象适配器的类图是这个样子滴:
看到没?和上边的类图就一个箭头的图形的差异,一个是继承,一个是关联,就这么多区别,只要把我们上面的程序稍微修改一下就成了类适配器,这个大家自己考虑一下,简单的很。适配器模式不适合在系统设计阶段采用,没有一个系统分析师会在做详设的时候考虑使用适配器模式,这个模式使用的主要场景是扩展应用中,就像我们上面的那个例子一样,系统扩展了,不符合原有设计的时候才考虑通过适配器模式减少代码修改带来的风险。
24种设计模式--适配器模式【Adapter Pattern】的更多相关文章
- 设计模式 - 适配器模式(adapter pattern) 具体解释
适配器模式(adapter pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 适配器模式(adapter pattern): 将一个类的接 ...
- 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释
适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...
- C#设计模式——适配器模式(Adapter Pattern)
一.概述在软件开发中,常常会想要复用一个已经存在的组件,但该组件的接口却与我们的需要不相符,这时我们可以创建一个适配器,在需复用的组件的接口和我们需要的接口间进行转换,从而能够正常的使用需复用的组件. ...
- 二十四种设计模式:适配器模式(Adapter Pattern)
适配器模式(Adapter Pattern) 介绍将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.示例有一个Message实体类 ...
- 怎样让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)
怎样让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 标签: 设计模式初涉 概念相关 定义: 适配器模式把一个类的接口变换成client所期待的还有一种接口,从而 使原本因接 ...
- 设计模式系列之适配器模式(Adapter Pattern)——不兼容结构的协调
模式概述 模式定义 模式结构图 模式伪代码 类适配器,双向适配器,缺省适配器 类适配器 双向适配器 缺省适配器 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 主要缺点 适 ...
- 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)
原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabc ...
- 【设计模式】适配器模式 Adapter Pattern
适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜.比如手机充电器,笔记本充电器,广播接收器,电视接收器等等.都是适配器. 适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作.比如 ...
- DP:***24种设计模式--转自刘伟
转自于高人的文章:http://blog.csdn.net/lovelion/article/details/17517213 2012年-2013年,Sunny在CSDN技术博客中陆续发表了100多 ...
随机推荐
- 2013 ACM区域赛长沙 C Collision HDU 4793
题意:在平面上0,0点,有一个半径为R的圆形区域,并且在0,0点固定着一个半径为RM(<R)的圆形障碍物,现在圆形区域外x,y,有一个半径 为r的,并且速度为vx,vy的硬币,如果硬币碰到了障碍 ...
- JavaScript浏览器本地数据存储
浏览器本地存储主要使用的是sessionStorage和localStorage.两者都支持,sessionStorage保存的是浏览器和服务器的一次对话信息,只在一次回话中有效.当在新标签页或新窗口 ...
- poj 1274 The Perfect Stall【匈牙利算法模板题】
The Perfect Stall Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 20874 Accepted: 942 ...
- 【LeetCode】Binary Tree Level Order Traversal II
Given a binary tree, return the bottom-up level order traversal of its nodes' values. (ie, from left ...
- getaccesstoken方法
通过appid和appsecret获取access_token的定义函数 这里用的是memcache缓存存储用户信息7000秒 <?php function getAccessToken($ap ...
- 在JSP页面中调用另一个JSP页面中的变量
在jsp学习中,经常需要在一个jsp页面中调用另一个jsp页面中的变量,下面就这几天的学习,总结一下. jsp页面之间的变量调用有多种方法: 1.通过jsp的内置对象—request对象获取参数: ( ...
- Java NIO使用及原理分析(二)
在第一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如 ...
- iOS中二维码的生成与使用(入门篇)
这里简单总结一下关于二维码的扫描与生成,用的是原生的AVFoundation框架,其实这个框架目前功能还是够用的,不过这里推荐一个二维码扫描的第三方(face++),网址就不贴了,直接度娘就OK,里面 ...
- 笔记本开了WIFI之后只能上QQ,上不了网页的解决方法
前几天拉了宽带之后,开了WIFI,发现WIFI能上网,但是电脑就上不了网页. 把本地连接的DNS指定一下,(电信)指定为202.102.192.68
- Fastjson介绍
简单介绍 Fastjson是一个Java语言编写的高性能功能完好的JSON库. 高性能 fastjson採用独创的算法,将parse的速度提升到极致,超过全部json库,包含以前号称最快的jackso ...