6 『适配器模式』 - 图1
image.png

1. 「引例」

  • 人力资源管理项目
  • 分为三大模块:人员信息管理薪酬管理职位管理。人员信息管理的对象是所有员工的所有信息,所有的员工指的是在职的员工,其他的离职的、退休的暂不考虑。

image.png

  • UserInfo存储用户的所有信息,也就是BO(Business Object,业务对象),这个对象设计为贫血对象「Thin Business Object」,不需要存储状态以及相关的关系。本人是反对使用充血对象「Rich Business Object」。
  • 一个对象如果不存储实体状态以及对象之间的关系,该对象就叫做「贫血对象」,对应的领域模型就是贫血领域模型,有实体状态和对象关系的模型就是充血领域模型。

  • 「接口」 ```java public interface IUserInfo { // 用户姓名 public String getUserName(); // 家庭地址 public String getHomeAddress(); // 手机号码 public String getMobileNumber(); // 办公电话 public String getOfficeTelNumber(); // 职位 public String getJobPosition(); // 家庭电话 public String getHomeTelNumber(); }

  1. - 「实现类」
  2. ```java
  3. public class UserInfo implements IUserInfo {
  4. public String getUserName() {
  5. System.out.println("姓名叫做...");
  6. return null;
  7. }
  8. public String getHomeAddress() {
  9. System.out.println("这里是员工的家庭地址...");
  10. return null;
  11. }
  12. public String getMobileNumber() {
  13. System.out.println("这个人的手机号码是0000...");
  14. return null;
  15. }
  16. public String getOfficeTelNumber() {
  17. System.out.println("办公室电话是...");
  18. return null;
  19. }
  20. public String getJobPosition() {
  21. System.out.println("这个人的职位是BOSS...");
  22. return null;
  23. }
  24. public String getHomeTelNumber() {
  25. System.out.println("员工的家庭电话是...");
  26. return null;
  27. }
  28. }
  • 现在出现一个劳动外包服务公司要加入到本项目中来,其公司的人员管理系统的业务模式跟本公司的模式并不同。

image.png

  • 人员信息分为三部分:基本信息办公信息个人家庭信息,并且 都放到了HashMap中,比如人员的姓名放到BaseInfo信息中,家庭地址放到HomeInfo中。

  • 「接口」

    1. public interface IOuterUser {
    2. //基本信息,如名称、性别、手机号码等
    3. public Map getUserBaseInfo();
    4. // 工作区域信息
    5. public Map getUserOfficeInfo();
    6. // 用户的家庭信息
    7. public Map getUserHomeInfo();
    8. }
  • 「实现类」

    1. public class OuterUser implements IOuterUser {
    2. public Map getUserBaseInfo() {
    3. HashMap baseInfoMap = new HashMap();
    4. baseInfoMap.put("userName", "这个员工叫混世魔王...");
    5. baseInfoMap.put("mobileNumber", "这个员工电话是...");
    6. return baseInfoMap;
    7. }
    8. public Map getUserOfficeInfo() {
    9. HashMap officeInfo = new HashMap();
    10. officeInfo.put("jobPosition", "这个人的职位是BOSS...");
    11. officeInfo.put("officeTelNumber", "员工的办公电话是...");
    12. return officeInfo;
    13. }
    14. public Map getUserHomeInfo() {
    15. HashMap homeInfo = new HashMap();
    16. homeInfo.put("homeTelNumbner", "员工的家庭电话是...");
    17. homeInfo.put("homeAddress", "员工的家庭地址是...");
    18. return homeInfo;
    19. }
    20. }
  • 现在需要将它们的管理系统与我们的进行交互。思路是先取出对方的数据对象,然后转化为我们的数据对象,中间需要转换。

  • 了解业务需求后,决定采用RMI「Remote Method Invocation,远程对象调用」的方式联机交互。只要有接口,就可以把远程的对象当成本地的对象使用。实现不同系统中的对象互相调用。

image.png
📌注:我们之所以能够增加一个OuterUserInfo中转类,是因为在系统设计时严格遵守了依赖倒置原则里氏替换原则,否则即使增加了中转类也无法解决问题。

  1. public class OuterUserInfo extends OuterUser implements IUserInfo {
  2. private Map homeInfo = super.getUserHomeInfo(); // 员工的基本信息
  3. private Map officeInfo = super.getUserOfficeInfo(); // 员工的家庭信息
  4. private Map baseInfo = super.getUserBaseInfo(); // 工作信息
  5. public String getUserName() {
  6. String userName = (String) this.baseInfo.get("userName");
  7. System.out.println(userName);
  8. return userName;
  9. }
  10. public String getHomeAddress() {
  11. String homeAddress = (String) this.homeInfo.get("homeAddress");
  12. System.out.println(homeAddress);
  13. return homeAddress;
  14. }
  15. public String getMobileNumber() {
  16. String mobileNumber = (String) this.baseInfo.get("mobileNumber");
  17. System.out.println(mobileNumber);
  18. return mobileNumber;
  19. }
  20. public String getOfficeTelNumber() {
  21. String officeTelNumber = (String) this.officeInfo.get("officeTelNumber");
  22. System.out.println(officeTelNumber);
  23. return officeTelNumber;
  24. }
  25. public String getJobPosition() {
  26. String jobPosition = (String) this.officeInfo.get("jobPosition");
  27. System.out.println(jobPosition);
  28. return jobPosition;
  29. }
  30. public String getHomeTelNumber() {
  31. String homeTelNumber = (String) this.homeInfo.get("homeTelNumber");
  32. System.out.println(homeTelNumber);
  33. return homeTelNumber;
  34. }
  35. }
  • 使用强转,完成数据转换。当然,一开始用泛型加以限制可以避免此转换。 ```java @Test public void test1() { // 没有与外系统连接的时候,是这样写的 IUserInfo userInfo = new UserInfo(); // 从数据库中查到10个 for (int i = 0; i < 10; i++) {
    1. userInfo.getMobileNumber();
    } }

@Test public void test2() { // 没有与外系统连接的时候,是这样写的 IUserInfo userInfo = new OuterUserInfo(); // 仅仅只用改动这里 // 从数据库中查到10个 for (int i = 0; i < 10; i++) { userInfo.getMobileNumber(); } }

  1. - 适配器模式只修改了一句话,其他的业务逻辑都不用修改就解决了系统
  2. 对接的问题。
  3. <a name="FTXYU"></a>
  4. # 2. 「定义」
  5. > Convert the interface of a class into another interface clients expect.Adapter lets classes work
  6. together that couldn't otherwise because of incompatible interfaces.
  7. - 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
  8. - 适配器模式(Adapter)又叫做**变压器模式 | 封装器模式**,也叫做**包装模式**(Wrapper),但是包装模式可不止一个,还包括了**装饰模式。**
  9. **通用类图**<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2860434/1619862720564-1764fbd2-82fe-451a-8c05-f4252838849f.png#clientId=u00992a08-7868-4&from=paste&id=u840264ce&margin=%5Bobject%20Object%5D&name=image.png&originHeight=369&originWidth=518&originalType=binary&size=42936&status=done&style=shadow&taskId=udc4d67bd-4ee4-482d-b1be-2bf0ba2e0e1)<br />形象解释:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2860434/1619863165976-00f347c8-a67c-41d7-9c66-2e6e948e8e81.png#clientId=u00992a08-7868-4&from=paste&id=u28da630a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=154&originWidth=1058&originalType=binary&size=32637&status=done&style=shadow&taskId=u32de5748-6be7-4aaa-b7e1-fafc5f0387c)
  10. - 「Target目标角色」
  11. - 该角色定义把其他类转换为何种接口,也就是期望接口,例子中的IUserInfo接口
  12. 就是目标角色。
  13. - 「Adaptee源角色」
  14. - 想把谁转换成目标角色,这个“谁”就是源角色。
  15. - 它是已经存在的、运行良好的类或对
  16. 象,经过适配器角色的包装,它会成为一个崭新、靓丽的角色。
  17. - 「Adapter适配器角色」
  18. - 适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建
  19. 立的。
  20. - 它的职责非常简单:把源角色转换为目标角色,怎么转换?通过继承或是类关联的方式。
  21. **通用源码**
  22. ```java
  23. public interface Target {
  24. public void request(); // 目标角色有自己的方法
  25. }
  26. public class ConcreteTarget implements Target {
  27. public void request() {
  28. System.out.println("If you need any help, call me!");
  29. }
  30. }
  31. public class Adaptee {
  32. //原有的业务逻辑
  33. public void doSomething() {
  34. System.out.println("I'm kind of busy, leave me alone.");
  35. }
  36. }
  37. public class Adapter extends Adaptee implements Target{
  38. public void request() {
  39. super.doSomething();
  40. }
  41. }
  42. class Client {
  43. @Test
  44. public void test() {
  45. // 原有的业务逻辑
  46. Target target = new ConcreteTarget();
  47. target.request();
  48. // 现在增加了适配器角色后的业务逻辑
  49. Target target2 = new Adapter();
  50. target2.request();
  51. }
  52. }

3. 「应用」

1. 「优点」

  • 适配器模式可以让两个没有任何关系的类在一起运行
    • 只要适配器这个角色能够搞定 他们就成。
  • 增加了类的透明性
    • 访问Target目标角色,但是具体的实现都委托给了源角色,而这些对高 层次模块是透明的,也是它不需要关心的。
  • 提高了类的复用度
    • 源角色在原有的系统中还是可以正常使用,而在目标角色中也可以充当新的演 员。
  • 灵活性非常好
    • 某一天,突然不想要适配器,删除掉这个适配器就可以,其他的代码都不用 修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。

2. 「使用场景」

  • 你有动机修改一个已经投产中的接口时,适 配器模式可能是最适合你的模式。
  • 比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口。使用适配器模式

3. 「注意事项」

📌

  • 适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处在开发阶段的问题, 而是解决正在服役的项目问题。这个模式使用的主要场景是扩展应用中,系统扩展了,不符合原有设计的时候才考虑通过适配器模式减少代码修改带来的风险。
  • 项目一定要遵守「依赖倒置原则」和「里氏替换原则」,否则即使在适合使用适配器的场合下,也会带来非常大的改造。

4. 「扩展」

  • 刚刚讲的人力资源管理的例子中,如果劳 动服务公司提供的人员接口不止一个,用户基本信息是一个接口,工作信息是一个接口,家庭信息是一个接口,总共有三个接口三个实现类,想想看如何处理呢?不能再使用我们上面的方法了。
  • 因为Java是不支持多继承,可以使用类关联的办法嘛。声明一个OuterUserInfo实现类,实现IUserInfo接口,通过再关联其他三个实现类就可以解决。

image.png

  • 「接口」

    1. public interface IOuterUserBaseInfo {
    2. public Map getUserBaseInfo(); //基本信息,比如名称、性别、手机号码等
    3. }
    4. public interface IOuterUserHomeInfo {
    5. public Map getUserHomeInfo(); //用户的家庭信息
    6. }
    7. public interface IOuterUserOfficeInfo {
    8. public Map getUserOfficeInfo(); //工作区域信息
    9. }
  • 「实现类」

    1. public class OuterUserBaseInfo implements IOuterUserBaseInfo {
    2. public Map getUserBaseInfo() {
    3. HashMap baseInfoMap = new HashMap();
    4. baseInfoMap.put("userName", "这个员工叫混世魔王...");
    5. baseInfoMap.put("mobileNumber", "这个员工电话是...");
    6. return baseInfoMap;
    7. }
    8. }
    9. public class OuterUserHomeInfo implements IOuterUserHomeInfo {
    10. public Map getUserHomeInfo() {
    11. HashMap homeInfo = new HashMap();
    12. homeInfo.put("homeTelNumbner", "员工的家庭电话是...");
    13. homeInfo.put("homeAddress", "员工的家庭地址是...");
    14. return homeInfo;
    15. }
    16. }
    17. public class OuterUserOfficeInfo implements IOuterUserOfficeInfo {
    18. public Map getUserOfficeInfo() {
    19. HashMap officeInfo = new HashMap();
    20. officeInfo.put("jobPosition", "这个人的职位是BOSS...");
    21. officeInfo.put("officeTelNumber", "员工的办公电话是...");
    22. return officeInfo;
    23. }
    24. }
  • 「适配器」

    1. public class OuterUserInfo implements IUserInfo {
    2. //源目标对象
    3. private IOuterUserBaseInfo baseInfo = null; //员工的基本信息
    4. private IOuterUserHomeInfo homeInfo = null; //员工的家庭信息
    5. private IOuterUserOfficeInfo officeInfo = null; //工作信息
    6. //数据处理
    7. private Map baseMap = null;
    8. private Map homeMap = null;
    9. private Map officeMap = null;
    10. public OuterUserInfo(IOuterUserBaseInfo _baseInfo, IOuterUserHomeInfo _homeInfo, IOuterUserOfficeInfo _officeInfo) {
    11. this.baseInfo = _baseInfo;
    12. this.homeInfo = _homeInfo;
    13. this.officeInfo = _officeInfo;
    14. //数据处理
    15. this.baseMap = this.baseInfo.getUserBaseInfo();
    16. this.homeMap = this.homeInfo.getUserHomeInfo();
    17. this.officeMap = this.officeInfo.getUserOfficeInfo();
    18. }
    19. public String getUserName() {
    20. String userName = (String) this.baseMap.get("userName");
    21. System.out.println(userName);
    22. return userName;
    23. }
    24. public String getHomeAddress() {
    25. String homeAddress = (String) this.homeMap.get("homeAddress");
    26. System.out.println(homeAddress);
    27. return homeAddress;
    28. }
    29. public String getMobileNumber() {
    30. String mobileNumber = (String) this.baseMap.get("mobileNumber");
    31. System.out.println(mobileNumber);
    32. return mobileNumber;
    33. }
    34. public String getOfficeTelNumber() {
    35. String officeTelNumber = (String) this.officeMap.get("officeTelNumber");
    36. System.out.println(officeTelNumber);
    37. return officeTelNumber;
    38. }
    39. public String getJobPosition() {
    40. String jobPosition = (String) this.officeMap.get("jobPosition");
    41. System.out.println(jobPosition);
    42. return jobPosition;
    43. }
    44. public String getHomeTelNumber() {
    45. String homeTelNumber = (String) this.homeMap.get("homeTelNumber");
    46. System.out.println(homeTelNumber);
    47. return homeTelNumber;
    48. }
    49. }
  • 「测试」

    1. class Client {
    2. @Test
    3. public void test() {
    4. //外系统的人员信息
    5. IOuterUserBaseInfo baseInfo = new OuterUserBaseInfo();
    6. IOuterUserHomeInfo homeInfo = new OuterUserHomeInfo();
    7. IOuterUserOfficeInfo officeInfo = new OuterUserOfficeInfo();
    8. // 传递三个对象
    9. IUserInfo iUserInfo = new OuterUserInfo(baseInfo, homeInfo, officeInfo);
    10. //从数据库中查到10个
    11. for (int i = 0; i < 10; i++) {
    12. iUserInfo.getMobileNumber();
    13. }
    14. }
    15. }
  • OuterUserInfo变成了委托服务,把IUserInfo接口 需要的所有的操作都委托给其他三个接口下的实现类,它的委托是通过对象层次的关联关系进行委托的,而不是继承关系。

  • 「拓展」里的这种通过关联类的方式实现适配器叫做「对象适配器」,通过继承进行适配的叫做「类适配器」

对象适配器通用类图
image.png

  • 对象适配器和类适配器的区别是:类适配器是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系,这是两者的根本区别。
  • 二者在实际项目中都会经常用到,由于对象适配器是通过类间的关联关系进行耦合的,因此在设计时就可以做到比较灵活,比如修补源角色的隐形缺陷,关联其他对象等,而类适配器就只能通过覆写源角色的方法进行扩 展,在实际项目中,对象适配器使用到场景相对较多。

5. 「最佳实践」

  • 在绝对完美的设计下,适配器模式根本用不到,如有设计上的疏漏,可以用适配器来修补。
  • 技术只是业务的实现工具,业务更替,技术的迭代也是难免的。用适配器就可以“弥补”这一需求。