使用适配器模式将异构系统同构化
适配器模式是一个我们会在不知不觉间使用的模式。
需求
假设本来我们的系统运行良好,已经上线。同时,我们的系统中包含了一个用户模块,可以用来查询用户。项目上线一段时间后,客户要求我们将用户系统对接到客户提供的用户系统上…这,怎么办呢?
用户模块现状
用户服务接口、实现以及我们自己的用户对象
- 用户服务接口:
/** * 用户查询Service * @author skyline */ public interface IUserService { /** * 通过用户名查询用户 * @param name * @return */ UserDTO getByName(String name); /** * 展示所有用户 * @return */ List<UserDTO> listAll(); }
- 我们自己的用户服务的实现
/** * 我的用户服务 */ public class MyUserService implements IUserService{ private final Map<String,UserDTO> userDTOMap; public MyUserService(){ userDTOMap = new HashMap<>(); init(); } private void init() { UserDTO zhangsan = new UserDTO(); zhangsan.setAge(18); zhangsan.setDept("部门A"); zhangsan.setName("zhangsan"); userDTOMap.put("zhangsan", zhangsan); UserDTO lisi = new UserDTO(); lisi.setAge(19); lisi.setDept("部门B"); lisi.setName("lisi"); userDTOMap.put("lisi", lisi); UserDTO wangwu = new UserDTO(); wangwu.setAge(20); wangwu.setDept("部门A"); wangwu.setName("wangwu"); userDTOMap.put("wangwu", wangwu); } @Override public UserDTO getByName(String name) { return userDTOMap.get(name); } @Override public List<UserDTO> listAll() { return new ArrayList<>(userDTOMap.values()); } }
- 我们自己的用户对象
/** * 用户对象 */ public class UserDTO { private String name; private Integer age; private String dept; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getDept() { return dept; } public void setDept(String dept) { this.dept = dept; } @Override public String toString() { return "UserDTO{" + "name='" + name + '\'' + ", age=" + age + ", dept='" + dept + '\'' + '}'; } }
客户新提供的用户接口以及用户对象
- 用户新提供的用户服务
/** * 假设这是来自OA sdk的一个类 * @author skyline */ public class OAUserService { private final Map<String,OAUserDTO> userDTOMap; public OAUserService() { userDTOMap = new HashMap<>(); init(); } private void init() { OAUserDTO zhangsan = new OAUserDTO(); zhangsan.setAge(18); zhangsan.setDeptName("部门A"); zhangsan.setLoginName("zhangsan"); userDTOMap.put("zhangsan",zhangsan); OAUserDTO lisi = new OAUserDTO(); lisi.setAge(19); lisi.setDeptName("部门B"); lisi.setLoginName("lisi"); userDTOMap.put("lisi",lisi); OAUserDTO wangwu = new OAUserDTO(); wangwu.setAge(20); wangwu.setDeptName("部门A"); wangwu.setLoginName("wangwu"); userDTOMap.put("wangwu",wangwu); } public OAUserDTO queryUser(String userName){ return userDTOMap.get(userName); } public List<OAUserDTO> getAllUser(){ return new ArrayList<>(userDTOMap.values()); } }
- 客户新提供的用户对象
/** * OA用户对象 */ public class OAUserDTO { private String loginName; private Integer age; private String deptName; public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } }
思考
- 这个需求虽然只是改造用户模块,但是由于用户模块会被各种其他模块调用,如果直接将
UserDTO
以及IUserService
都替换掉,那将会是一个影响巨大的改造,同时将引入极大的不确定性。 - 基于上面的考虑,我们需要尽可能的复用
UserDTO
和IUserService
- 写一个新的
IUserService
的实现,来调用客户提供的用户接口OAUserService
- 将用户提供的用户对象
OAUserDTO
想办法转换成UserDTO
改造
提供一个类,将IUserService
、UserDTO
、OAUserService
以及OAUserDTO
关联起来。
/**
* OA用户服务适配器
* @author skyline
*/
public class OAUserServiceAdapter implements IUserService{
private final OAUserService oaUserService;
public OAUserServiceAdapter() {
oaUserService = new OAUserService();
}
@Override
public UserDTO getByName(String name) {
OAUserDTO oaUserDTO = oaUserService.queryUser(name);
return convert(oaUserDTO);
}
private UserDTO convert(OAUserDTO oaUserDTO) {
UserDTO userDTO = new UserDTO();
userDTO.setName(oaUserDTO.getLoginName());
userDTO.setDept(oaUserDTO.getDeptName());
userDTO.setAge(oaUserDTO.getAge());
return userDTO;
}
@Override
public List<UserDTO> listAll() {
List<OAUserDTO> allUser = oaUserService.getAllUser();
return allUser.stream().map(this::convert).collect(Collectors.toList());
}
}
在上面的改造中,OAUserServiceAdapter
实现了IUserService
接口,但是内部的业务逻辑委托给OAUserService
处理,当OAUserService
处理完毕后,再通过convert
方法将OAUserDTO
转换成UserDTO
。这样,只要IUserService
在实例化时使用OAUserServiceAdapter
的实例,系统的用户模块就可以无缝迁移到客户提供的新模块上。
测试
一开始的时候,系统使用内置的用户模块:
改造完毕后,系统使用客户提供的新模块,但是业务代码却不需要做调整:
适配器模式
适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
总结
- 适配器模式分为类适配器和对象适配器,上面的列子中这两种适配器思想都得到了体现。
- 适配器模式满足开闭原则,代码改造量小,同时相当灵活。
- 过多的使用适配器容易导致系统复杂度上升,同时对象适配器也容易产生非常多的零散对象。
- 如果可能的话,从长远来看,适当的对整体系统重构要好于使用适配器模式。