适配器模式也是最常用的模式之一,也很简单。
每个笔记本接电源的线上都有一个黑盒子,它就是适配器,无论你是吧电脑接在在220V的电源上还是110V的电源上,通过这个适配器把这些不同的电压转换成电脑需要的36V电压,保证笔记本在一个电压稳定的情况下正常运行。今天我要讲的这个适配器模式也是这个意思。一样的作用,两个不同的接口,有不同的实现,在某一天God说,你要把B接口转化为A接口,What should we do?
继承,确实能解决,但不是明智的选择,而且违背了OCP原则。So……
So 采用适配器模式就能accomplish perfectly。
适配器的通用类图长这个样(从RationalRose 的帮助文档截图的):
看完图,大家肯定有个疑惑,这不是就包装模式(Wrapper)吗?Bingo!但是包装模式可不止一个,装饰模式也是包装模式,这个先跳过,回到正题Adapter。
Adapter只要分为三大类:类适配器模式、对象适配器模式和接口适配器模式
为了能以一种愉快的方式把Adapter吃透,我们直接通过具体例子来说吧!
一个人力资源的项目分三大模块:人员信息管理、薪酬管理和职位管理,其中人员信息管理中涉及到了适配器模式。
是这么一回事,一开始开发时,人员信息管理的对象是所有员工的所有信息,我们涉及的员工类是这样滴:
后来,公司人手不足,和XX公司借用了一批人员,然后boss 就要求增加一个功能借用人员的管理。
OK,很容易呀,直接把借用人员信息从他们公司传输到我们系统中就好了……事情要是这么简单多没趣呀!
我们需要把这些借用人员信息传输到我们的系统中,系统之间的传输使用RMI(Remote Method Invacation,远程对象调用),
问题是他们公司和我们公司对人员对象的定义不同。他们是这样滴:
由上面的类图可以知道,我们的家庭住址是在他们的HomeInfo中,UserName是在BaseInfo中,问题来了,两个系统对人员对象的定义不统一怎么实现两个系统的交互呢?
解决方案:当然是我们的杀手锏Adapter啦!
但是又有问题啦,两个对象不在一个系统中,怎么一起用?
So easy!RMI已经帮我们做好了,只要有接口,就可以远程的对象当成本地的对象使用(了解更多可以直接去看RMI文档)。
通过适配器,把outerUser伪装成我们系统中一个IUserInfo对象,这样我们原来的系统基本不用怎么改,所有人员查询、调用和本地一样。
不多说了,下面上代码:
接口IUserInfo.java
package com.freedom.adapter;
public interface IUserInfo {
// 用户名
public String getUserName();
// 家庭地址
public String getHomeAddress();
// 手机号
public String getMobileNumber();
// 办公电话
public String getOfficeTelNumber();
// 职位
public String getJobPosition();
// 家庭电话
public String getHomeTelNumber();
}
接口的实现类UserInf.java
package com.freedom.adapter;
public class UserInfo implements IUserInfo {
@Override
public String getUserName() {
System.out.println("用户名");
return null;
}
@Override
public String getOfficeTelNumber() {
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 getJobPosition() {
System.out.println("职位");
return null;
}
@Override
public String getHomeTelNumber() {
System.out.println("家庭电话");
return null;
}
}
可能有人要问了,为什么要把电话号码、手机号码都设置成String 类型,而不是int 类型,你说呢?题外话,这个绝对应该是String 类型,包括数据库也应该是varchar 类型的,手机号码有带区号的,比如+8613267581356,这个你用数字怎么表示?继续看代码,系统的应用如何调用UserInfo 的信息呢?
APP.java
package com.freedom.adapter;
public class App {
public static void main(String[] args) {
// 没有与外系统连接的时候,是这样写的
IUserInfo user = new UserInfo();
// 从数据库中查到101个
for (int i = 0; i < 101; i++) {
user.getMobileNumber();
}
}
}
从数据库中生成了101 个UserInfo对象,直接打印出来就成了。那然后增加了外系统的人员信息,怎么处理呢?
IOuterUser.java :
package com.freedom.adapter;
import java.util.Map;
public interface IOuterUser {
// 基本信息,比如名称,性别,手机号码了等
public Map getUserBaseInfo();
// 工作区域信息
public Map getUserOfficeInfo();
// 用户的家庭信息
public Map getUserHomeInfo();
}
怎么把外系统的用户信息包装成我们公司的人员信息?
OuterUserInfo.java(适配器):
package com.freedom.adapter;
import java.util.Map;
public class OuterUserInfo extends OuterUser implements IUserInfo {
private Map baseInfo = super.getUserBaseInfo(); // 员工的基本信息
private Map homeInfo = super.getUserHomeInfo(); // 员工的家庭信息
private Map officeInfo = super.getUserOfficeInfo(); // 工作信息
public String getHomeAddress() {
String homeAddress = (String) this.homeInfo.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
}
public String getHomeTelNumber() {
String homeTelNumber = (String) this.homeInfo.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
}
public String getJobPosition() {
String jobPosition = (String) this.officeInfo.get("jobPosition");
System.out.println(jobPosition);
return jobPosition;
}
public String getMobileNumber() {
String mobileNumber = (String) this.baseInfo.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
}
public String getOfficeTelNumber() {
String officeTelNumber = (String) this.officeInfo
.get("officeTelNumber");
System.out.println(officeTelNumber);
return officeTelNumber;
}
public String getUserName() {
String userName = (String) this.baseInfo.get("userName");
System.out.println(userName);
return userName;
}
}
大家看到没?这里有很多的强制类型转换,就是(String)这个东西,如果使用泛型的话,完全就可以避免这个转化,泛型以后再详细讲。这个适配器的作用就是做接口的转换。
最后我们再来看看我们的业务是怎么调用的:
package com.freedom.adapter;
public class App {
public static void main(String[] args) {
// 没有与外系统连接的时候,是这样写的
//IUserInfo user = new UserInfo();
//与外系统连接的时候,只修改了这一句
IUserInfo user = new OuterUserInfo();
// 从数据库中查到101个
for (int i = 0; i < 101; i++) {
user.getMobileNumber();
}
}
}
使用了适配器模式只修改了一句话,其他的业务逻辑都不用修改就解决了系统对接的问题,而且在我们实际系统中只是增加了一个业务类的继承,就实现了可以查本公司的员工信息,也可以外借公司的员工信息,尽量少的修改,通过扩展的方式解决了问题。
适配器模式分为类适配器和对象适配器,这个区别不大,上边的例子就是类适配器,那对象适配器是什么样子呢?
对象适配器的类图是这个样子滴:
看到没?和上边的类图就一个箭头的图形的差异,一个是继承,一个是关联,就这么多区别,只要把我们上面的程序稍微修改一下就成了对象适配器。
适配器模式不适合在系统设计阶段采用,没有一个系统分析师会在做详细设计的时候考虑使用适配器模式,就像我们上面的那个例子一样,系统扩展了,不符合原有设计的时候才考虑通过适配器模式减少代码修改带来的风险。
讲了这么多,总结一下三种适配器模式的应用场景:
类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
最后小结一下适配器模式的优缺点:
1.优点:
1)将目标类和适配者类解耦
2)增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
3)灵活性和扩展性都非常好,符合开闭原则
2.类适配器还有的优点:
由于适配器类是适配者类的子类,因此可以再适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
3.类适配器的缺点:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和他的子类同时适配到目标接口。
4.对象适配器还有的优点:
把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口。
5.对象适配器的缺点:
与类适配器模式相比,要想置换适配者类的方法就不容易。
http://download.youkuaiyun.com/detail/github_22022001/8290561