说说啥是适配器
适配器模式是设计模式中比较好理解的设计模式之一。适配器,通俗来说就有点像生活中插座的转接头,你有一个三孔插座,但是你的电视插头却是两孔的,这时加上一个转接头就能让插头正常工作。其实,适配器模式的思想也就源于此,在面向对象的代码中,有很多可复用的类(经过反复测试可用),但是有时候我们去使用的时候却必须去改动一些地方,这就需要我们重新去测试,如此反复浪费时间与效率,于是大佬们就想出了一种设计模式——适配器模式,我们去做一个转接头,把那些可复用的类包装成目标对象不就可以了吗?于是,适配器模式应运而生。
适配器模式的定义
Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)Adapter模式又被称为Wrapper模式(包装器),它有以下两种:
-
类适配器模式(使用继承的适配器) 类图如下:
-
对象适配器模式(使用委托的适配器) 类图如下:
对适配器中各个登场角色的分析
- Target对象:该对象定义把其他类转化为何种接口,也就是我们的期望接口。如下面案例中的IUserInfo接口就是目标对象
- Adaptee源角色:被适配的对象,它是已经存在的,运转良好的类或对象。如下面案例中的IOuterUser接口
- Adapter适配器角色:适配器模式的核心角色,通过类继承或类关联的方式将源角色转换为目标角色
- Client角色:该角色负责使用Target角色所定义的方法进行具体处理
案例引入(来自《设计模式之禅》)——类适配器模式
某公司做了一个人力资源管理项目,共分为三大模块:人员信息管理,薪酬管理,职位管理。其中,人员信息管理的对象是所有员工的所有信息(指在职的员工,离职退休的员工不考虑),人员信息管理类图如下:
接口设计如下:
//人员信息管理模块接口(包含员工的基本信息)
public interface IUserInfo {
//获得用户姓名
public String getUserName();
//获得家庭地址
public String getHomeAddress();
//手机号码
public String getMobileNumber();
//办公电话
public String getOfficeTelNumber();
//职位
public String getJobPosition();
//获得家庭电话
public String getHomeTelNumber();
}
上面代码是信息管理模块的接口设计,具体实现类没有给出。现在遇到了一个问题,公司需要从劳动服务公司引进一部分员工解决公司劳动力不足问题,就需要将他们的基本信息比如:人员信息,工资情况,福利情况等同步到本公司的人力资源管理系统中来(人力资源部门要求我们的系统同步劳动服务公司这部分员工的信息),但是经过调研发现,劳动服务公司的人员对象和本公司系统的对象不相同,劳动服务公司人员信息管理类图如下:
//接口设计
public interface IOuterUser {
//基本信息, 比如名称、 性别、 手机号码等
public Map<String, String> getUserBaseInfo();
//工作区域信息
public Map<String, String> getUserOfficeInfo();
//用户的家庭信息
public Map<String, String> getUserHomeInfo();
}
//接口的实现类
public class OuterUser implements IOuterUser {
/*
* 用户的基本信息
*/
public Map<String, String> getUserBaseInfo() {
HashMap<String, String> baseInfoMap = new HashMap<>();
baseInfoMap.put("userName", "这个员工叫混世魔王...");
baseInfoMap.put("mobileNumber", "这个员工电话是...");
return baseInfoMap;
}
/
**
员工的家庭信息
*/
public Map getUserHomeInfo() {
HashMap<String, String> homeInfo = new HashMap<>();
homeInfo.put("homeTelNumbner", "员工的家庭电话是...");
homeInfo.put("homeAddress", "员工的家庭地址是...");
return homeInfo;
}
/
**
员工的工作信息, 比如, 职位等
*/
public Map<String, String> getUserOfficeInfo() {
HashMap officeInfo = new HashMap();
officeInfo.put("jobPosition","这个人的职位是BOSS...");
officeInfo.put("officeTelNumber", "员工的办公电话是...");
return officeInfo;
}
}
分析如上设计我们发现:劳动服务公司将人员信息分为了三部分:基本信息,办公信息和个人家庭信息,并且都放到HashMap中。现在的问题是,本公司的人员信息管理系统如何和劳服公司的系统进行交互呢?这时可以进行这样的转化,先拿到对方的数据对象,然后转化为我们自己的数据对象,中间加一层数据转换处理,类图如下:
//适配器类OuterUserInfo的实现如下
public class OuterUserInfo extends OuterUser implements IUserInfo{
private Map<String, String> baseInfo = super.getUserBaseInfo();//员工的基本信息
private Map<String, String> homeInfo = super.getUserHomeInfo(); //员工的家庭信息
private Map<String, String> officeInfo = super.getUserOfficeInfo(); //员工的工作信息
/*
* 家庭地址
*/
public String getHomeAddress() {
String homeAddress = this.homeInfo.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
}
/*
*家庭电话号码
*/
public String getHomeTelNumber() {
String homeTelNumber = this.homeInfo.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
}
/*
*职位信息
*/
public String getJobPosition() {
String jobPosition = this.officeInfo.get("jobPosition");
System.out.println(jobPosition);
return jobPosition;
}
/*
*手机号码
*/
public String getMobileNumber() {
String mobileNumber = this.baseInfo.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
}
/*
*办公电话
*/
public String getOfficeTelNumber() {
String officeTelNumber = this.officeInfo.get("officeTelNumbe");
System.out.println(officeTelNumber);
return officeTelNumber;
}
/*
*员工的名称
*/
public String getUserName() {
String userName = this.baseInfo.get("userName");
System.out.println(userName);
return userName;
}
}
//主类如下
public class Client {
public static void main(String[] args) {
//1.没有与外系统共享时
IUserInfo girl = new UserInfo();
girl.getMobileNumber();
//2.与外系统共享
IUserInfo girl2 = new OuterUserInfo();
girl2.getMobileNumber();
}
}
这就是适配器的强大之处,通过使用适配器,我们几乎不用对原来的系统和要包装的系统做任何修改,就能将两个系统很完美的拼接在一起,只要在主类修改一句话就能解决问题。
使用对象适配器模式
我们还是使用上面的案例,我们想一想,假如劳动服务公司给的员工信息接口是分开的,比如基本信息一个接口,家庭信息一个接口等有多个接口的情况,我们还能像上面那样做吗?当然不行,因为Java是不支持多继承的,我们可以使用委托(也就是一种关联关系)来达到目的,这就是适配器模式的另一种方式:对象适配器模式。类图如下:
各个接口和实现如下
public interface IOuterUserBaseInfo {
//基本信息, 比如名称、 性别、 手机号码等
public Map<String, String> getUserBaseInfo();
}
public interface IOuterUserHomeInfo {
//用户的家庭信息
public Map<String, String> getUserHomeInfo();
}
public interface IOuterUserOfficeInfo {
//工作区域信息
public Map<String, String> getUserOfficeInfo();
}
public class OuterUserBaseInfo implements IOuterUserBaseInfo {
/*
* 用户的基本信息
*/
public Map<String, String> getUserBaseInfo() {
HashMap<String, String> baseInfoMap = new HashMap<>();
baseInfoMap.put("userName", "这个员工叫混世魔王...");
baseInfoMap.put("mobileNumber", "这个员工电话是...");
return baseInfoMap;
}
public class OuterUserHomeInfo implements IOuterUserHomeInfo {
/*
* 员工的家庭信息
*/
public Map<String, String> getUserHomeInfo() {
HashMap<>String, String> homeInfo = new HashMap<>();
homeInfo.put("homeTelNumbner", "员工的家庭电话是...");
homeInfo.put("homeAddress", "员工的家庭地址是...");
return homeInfo;
}
}
public class OuterUserOfficeInfo implements IOuterUserOfficeInfo {
/*
* 员工的工作信息, 比如, 职位等
*/
public Map<String, String> getUserOfficeInfo() {
HashMap<String, String> officeInfo = new HashMap<>();
officeInfo.put("jobPosition","这个人的职位是BOSS...");
officeInfo.put("officeTelNumber", "员工的办公电话是...");
return officeInfo;
}
}
}
下面是适配器及主类
public class OuterUserInfo implements IUserInfo {
//源目标对象
private IOuterUserBaseInfo baseInfo = null; //员工的基本信息
private IOuterUserHomeInfo homeInfo = null; //员工的家庭信息
private IOuterUserOfficeInfo officeInfo = null; //工作信息
//数据处理
private Map<String, String> baseMap = null;
private Map<String, String> homeMap = null;
private Map<String, String> officeMap = null;
//构造函数传递对象
public OuterUserInfo(IOuterUserBaseInfo baseInfo,IOuterUserHomeInfo homeInfo,
IOuterUserOfficeInfo officeInfo){
this.baseInfo = baseInfo;
this.homeInfo = homeInfo;
this.officeInfo = officeInfo;
//数据处理
this.baseMap = this.baseInfo.getUserBaseInfo();
this.homeMap = this.homeInfo.getUserHomeInfo();
this.officeMap = this.officeInfo.getUserOfficeInfo();
}
//家庭地址
public String getHomeAddress() {
String homeAddress = this.homeMap.get("homeAddress");
System.out.println(homeAddress);
return homeAddress;
}
//家庭电话号码
public String getHomeTelNumber() {
String homeTelNumber = this.homeMap.get("homeTelNumber");
System.out.println(homeTelNumber);
return homeTelNumber;
}
//职位信息
public String getJobPosition() {
String jobPosition = this.officeMap.get("jobPosition");
System.out.println(jobPosition);
return jobPosition;
}
//手机号码
public String getMobileNumber() {
String mobileNumber = this.baseMap.get("mobileNumber");
System.out.println(mobileNumber);
return mobileNumber;
}
//办公电话
public String getOfficeTelNumber() {
String officeTelNumber= this.officeMap.get("officeTelNumber"
System.out.println(officeTelNumber);
return officeTelNumber;
}
//员工的名称
public String getUserName() {
String userName = this.baseMap.get("userName");
System.out.println(userName);
return userName;
}
}
//主类如下:
public class Client {
public static void main(String[] args) {
//外系统的人员信息
IOuterUserBaseInfo baseInfo = new OuterUserBaseInfo();
IOuterUserHomeInfo homeInfo = new OuterUserHomeInfo();
IOuterUserOfficeInfo officeInfo = new OuterUserOfficeInfo();
//传递三个对象
IUserInfo girl = new OuterUserInfo(baseInfo,homeInfo,officeInfo)
girl.getMobileNumber();
}
适配器模式的优点
- 适配器模式可以让两个没有任何关系的类在一起运行
- 增加了类的透明性
- 提高了类的复用度:源角色在原有的系统中还可以正常使用,在目标角色中也可以充当新的演员
- 具有很高的灵活性
注意: 在使用适配器模式时,项目一定要遵循依赖倒置原则和里式替换原则。
参考书籍
- 《设计模式之禅》
- 《图解设计模式》