设计模式之禅之设计模式-适配器模式
一:适配器模式的定义
--->一个补救模式,这种模式可以让你从因业务扩展而系统无法迅速适应的苦恼中解脱而出。
--->贫血对象和充血对象,这两个名词很简单,在领域模型中分别叫做贫血领域模型和充血领域模型,有什么区别呢?一个对象如果不存储实体状
态以及对象之间的关系,该对象就叫做贫血对象,对应的领域模型就是贫血领域模型,有实体状态和对象关系的模型就是充血领域模型
--->将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
--->适配器模式又叫做变压器模式,也叫做包装模式(Wrapper),但是包装模式可不止一个,还包括了第17章讲解的装饰模式
二:适配器模式的角色
● Target目标角色(行为的发起方的接口)
该角色定义把其他类转换为何种接口,也就是我们的期望接口。
● Adaptee源角色(行为的发起方不兼容的实现类)
你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。
● Adapter适配器角色(行为发起方的代理角色。通过它,与不兼容方的实现进行转换,得到原有接口想要的数据)
适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标角色,怎么转换?通过继承或是类关联的方式。
●如上图,B想用A的数据。但由于不兼容。B用C做适配器和A沟通,C内部转化形成B能识别的数据。
三:适配器模式的应用
适配器模式的优点
● 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。
● 增加了类的透明性
想想看,我们访问的Target目标角色,但是具体的实现都委托给了源角色,而这些对高层次模块是透明的,也是它不需要关心的。
● 提高了类的复用度
当然了,源角色在原有的系统中还是可以正常使用,而在目标角色中也可以充当新的演员。
● 灵活性非常好
某一天,突然不想要适配器,没问题,删除掉这个适配器就可以了,其他的代码都不用修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。
四:适配器模式的应用场景
适配器应用的场景只要记住一点就足够了:你有动机修改一个已经投产中的接口时,适配器模式可能是最适合你的模式。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?使用适配器模式,这也是我们例子中提到的。
五:适配器模式的注意事项
适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处在开发阶段的问题,而是解决正在服役的项目问题,没有一个系统分析师会在做详细设计的时候考虑使用适配器模式,这个模式使用的主要场景是扩展应用中,就像我们上面的那个例子一样,系统扩展了,不符合原有设计的时候才考虑通过适配器模式减少代码修改带来的风险。再次提醒一点,项目一定要遵守依赖倒置原则和里氏替换原则,否则即使在适合使用适配器的场合下,也会带来非常大的改造。
六:适配器模式的例子
【1】目标角色的接口(行为的发起方,试图兼容别的接口)
1 package com.yeepay.sxf.template14; 2 /** 3 * 目标角色 4 * 行为的发起方 5 * @author sxf 6 * 7 */ 8 public interface IUserInfo { 9 //获取名字 10 public String getNameById(Integer id); 11 //根据名字获取地址 12 public String getAddressBydName(String name); 13 }
【2】目标角色原来的实现类
1 package com.yeepay.sxf.template14; 2 /** 3 * 目标角色现有的实现类 4 * 原有的规律 5 * @author sxf 6 * 7 */ 8 public class UserInfo implements IUserInfo{ 9 10 @Override 11 public String getNameById(Integer id) { 12 System.out.println("UserInfo.getNameById(与数据库交互返回名字sxf)"); 13 return "sxf"; 14 } 15 16 @Override 17 public String getAddressBydName(String name) { 18 System.out.println("UserInfo.getAddressByName(与数据库交互返回名字sxd)"); 19 return "smx"; 20 } 21 22 23 }
【3】源角色,(想被目标兼容的接口)
1 package com.yeepay.sxf.template14; 2 3 import java.util.Map; 4 5 /** 6 * 源角色的接口 7 * 不兼容的接口 8 * @author sxf 9 * 10 */ 11 public interface OutUserInfo { 12 //根据id获取用户名 13 public Map<String, String> getNameById(String id); 14 //根据用户名获取地址 15 public Map<String, String> getAddressByName(String name); 16 }
【4】源角色实现类,(想被目标兼容的接口的实现类)
1 package com.yeepay.sxf.template14; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /** 7 * 源角色 8 * @author sxf 9 * 10 */ 11 public class OutUserInfoimpl implements OutUserInfo{ 12 13 @Override 14 public Map<String, String> getNameById(String id) { 15 Map<String, String> map=new HashMap<String, String>(); 16 map.put("12345", "sxf"); 17 map.put("1236", "hdd"); 18 return map; 19 } 20 21 @Override 22 public Map<String, String> getAddressByName(String name) { 23 Map<String, String> map=new HashMap<String, String>(); 24 map.put("sxf", "smx"); 25 map.put("hdd", "hnzz"); 26 return map; 27 } 28 29 30 }
【5】适配器
1 package com.yeepay.sxf.template14; 2 3 import java.util.Map; 4 5 /** 6 * 适配器 7 * 继承源数据的实现类, 8 * 实现目标类的接口 9 * @author sxf 10 * 11 */ 12 public class UserInfoAdapter extends OutUserInfoimpl implements IUserInfo{ 13 14 @Override 15 public String getNameById(Integer id) { 16 //转换适配 17 Map<String, String> map=super.getNameById(String.valueOf(id)); 18 //获取名字 19 String name=map.get(String.valueOf(id)); 20 return name; 21 } 22 23 @Override 24 public String getAddressBydName(String name) { 25 //转换适配 26 Map<String, String> map=super.getAddressByName(name); 27 //获取地址 28 String address=map.get(name); 29 return address; 30 } 31 32 33 }
【6】客户端测试
1 package com.yeepay.sxf.template14; 2 /** 3 * 客户端测试 4 * 以前业务类,从本数据库获取用户信息 5 * 现在的业务,要从别的系统获取用户信息。别的系统的用户信息的接口返回值不兼容。 6 * 用适配器模式兼容别的用户系统的接口 7 * @author sxf 8 * 9 */ 10 public class ClientTest { 11 12 public static void main(String[] args) { 13 //原有业务 14 IUserInfo userInfo=new UserInfo(); 15 //获取名字 16 String nameString=userInfo.getNameById(1234); 17 //获取地址 18 String addresString=userInfo.getAddressBydName(nameString); 19 System.out.println("ClientTest.main()"+nameString); 20 System.out.println("ClientTest.main()"+addresString); 21 22 23 24 //现有变动业务,要远程RMI调用别的系统的用户 25 IUserInfo userInfo2=new UserInfoAdapter(); 26 //获取别的系统的名字 27 String nameString2=userInfo2.getNameById(12345); 28 //获取别的系统的地址 29 String addreString2=userInfo2.getAddressBydName(nameString2); 30 System.out.println("ClientTest.main()"+nameString2); 31 System.out.println("ClientTest.main()"+addreString2); 32 33 } 34 }