设计模式-结构型-适配器模式

1. 适配器模式原理和实现

  • Adapter Design Pattern

    • 做适配的,将不兼容的接口转换为可兼容的接口
    • 将原本由于接口不兼容而不能一起工作的类一起工作
  • 实现方式

    • 类适配器
      • 使用继承关系实现
    • 对象适配器
      • 使用组合关系实现
// 类适配器: 基于继承
public interface ITarget {
  void f1();
  void f2();
  void fc();
}

public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
}

public class Adaptor extends Adaptee implements ITarget {
  public void f1() {
    super.fa();
  }

  public void f2() {
    //...重新实现f2()...
  }

  // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}

// 对象适配器:基于组合
public interface ITarget {
  void f1();
  void f2();
  void fc();
}

public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
}

public class Adaptor implements ITarget {
  private Adaptee adaptee;

  public Adaptor(Adaptee adaptee) {
    this.adaptee = adaptee;
  }

  public void f1() {
    adaptee.fa(); //委托给Adaptee
  }

  public void f2() {
    //...重新实现f2()...
  }

  public void fc() {
    adaptee.fc();
  }
}
  • 类适配器和对象适配器的选择取决于

    • Adaptee接口的个数
    • Adaptee 和Itarget的契合程度
  • 如果 Adaptee 接口并不多,那两种实现方式都可以。

  • 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。

  • 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。

2. 应用场景总结

  • 适配器模式 – 一种补偿模式
    • 主要用来弥补一些设计上的缺陷
    • 主要用来解决接口不兼容的问题

2.1 封装有缺陷的接口设计

譬如我们依赖的外部系统在接口设计上有缺陷(包含大量的静态方法),引入后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计。

public class CD { //这个类来自外部sdk,我们无权修改它的代码
  //...
  public static void staticFunction1() { //... }

  public void uglyNamingFunction2() { //... }

  public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... }

   public void lowPerformanceFunction4() { //... }
}

// 使用适配器模式进行重构
public class ITarget {
  void function1();
  void function2();
  void fucntion3(ParamsWrapperDefinition paramsWrapper);
  void function4();
  //...
}
// 注意:适配器类的命名不一定非得末尾带Adaptor
public class CDAdaptor extends CD implements ITarget {
  //...
  public void function1() {
     super.staticFunction1();
  }

  public void function2() {
    super.uglyNamingFucntion2();
  }

  public void function3(ParamsWrapperDefinition paramsWrapper) {
     super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
  }

  public void function4() {
    //...reimplement it...
  }
}

2.2 统一多个类的接口设计

某个功能的实现依赖多个外部系统,通过适配器模式,将其接口适配为统一的接口定义,然后使用多态的特性来复用代码逻辑。

下述代码使用适配器模式来处理有不同的接口设计的几个来自第三方做词汇过滤的API

public class ASensitiveWordsFilter { // A敏感词过滤系统提供的接口
  //text是原始文本,函数输出用***替换敏感词之后的文本
  public String filterSexyWords(String text) {
    // ...
  }

  public String filterPoliticalWords(String text) {
    // ...
  } 
}

public class BSensitiveWordsFilter  { // B敏感词过滤系统提供的接口
  public String filter(String text) {
    //...
  }
}

public class CSensitiveWordsFilter { // C敏感词过滤系统提供的接口
  public String filter(String text, String mask) {
    //...
  }
}

// 未使用适配器模式之前的代码:代码的可测试性、扩展性不好
public class RiskManagement {
  private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
  private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
  private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();

  public String filterSensitiveWords(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    maskedText = bFilter.filter(maskedText);
    maskedText = cFilter.filter(maskedText, "***");
    return maskedText;
  }
}

// 使用适配器模式进行改造
public interface ISensitiveWordsFilter { // 统一接口定义
  String filter(String text);
}

public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
  private ASensitiveWordsFilter aFilter;
  public String filter(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    return maskedText;
  }
}
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...

// 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统,
// 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。
public class RiskManagement { 
  private List<ISensitiveWordsFilter> filters = new ArrayList<>();

  public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
    filters.add(filter);
  }

  public String filterSensitiveWords(String text) {
    String maskedText = text;
    for (ISensitiveWordsFilter filter : filters) {
      maskedText = filter.filter(maskedText);
    }
    return maskedText;
  }
}

2.3 替换依赖的外部系统

// 外部系统A
public interface IA {
  //...
  void fa();
}
public class A implements IA {
  //...
  public void fa() { //... }
}
// 在我们的项目中,外部系统A的使用示例
public class Demo {
  private IA a;
  public Demo(IA a) {
    this.a = a;
  }
  //...
}
Demo d = new Demo(new A());

// 将外部系统A替换成外部系统B
public class BAdaptor implemnts IA {
  private B b;
  public BAdaptor(B b) {
    this.b= b;
  }
  public void fa() {
    //...
    b.fb();
  }
}
// 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,
// 只需要将BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));

2.4 兼容老版本的接口

做版本升级的时候,对于一些要废弃的接口,不能直接将其删除,而是暂时保留,并且标注为deprecated,并将内部实现逻辑委托为新的接口实现。这样就可以是的项目有个过渡期。

Enumeration –> Iterator的升级

public class Collections {
  public static Emueration emumeration(final Collection c) {
    return new Enumeration() {
      Iterator i = c.iterator();

      public boolean hasMoreElments() {
        return i.hashNext();
      }

      public Object nextElement() {
        return i.next():
      }
    }
  }
}

3. 代理 vs 桥接 vs 装饰器 vs 适配器

  • 代理模式

    • 在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是访问控制,而非加强功能
  • 桥接模式

    • 将接口部分和实现部分分离,使得其能够相对独立的进行改变
  • 装饰器模式

    • 在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用
  • 适配器模式

    • 事后补救策略,适配器提供跟原始类不同的接口

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com

文章标题:设计模式-结构型-适配器模式

文章字数:1.5k

本文作者:Leilei Chen

发布时间:2020-06-24, 11:55:54

最后更新:2020-06-23, 11:56:23

原始链接:https://www.llchen60.com/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E7%BB%93%E6%9E%84%E5%9E%8B-%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏