Java 设计模式(九):适配器模式

适配器模式

GitHub 地址:https://github.com/yifanzheng/java-design-patterns

适配器模式(Adapter Design Pattern),将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。

适配器模式解析

适配器模式有两种实现方式:对象适配器和类适配器。其中,对象适配器使用组合关系来实现,类适配器使用继承关系来实现。下面我们就来看看具体的代码实现吧。

其中,Target 是客户所期待的接口定义,可以是具体的或抽象的类,也可以是接口。Adaptee 是需要适配的类。Adapter 是将 Adaptee 包装成符合 Target 接口定义的接口。

对象适配器实现

public interface Target {
    void request();
}

public class Adaptee {

    public void specificRequest() {
        System.out.println("特殊请求!");
    }
}

public class Adapter implements Target {

    private Adaptee adaptee;

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

    @Override
    public void request() {
       this.adaptee.specificRequest();
       // 省略代码...
    }
}

类适配器实现

public interface Target {
    void request();
}

public class Adaptee {

    public void specificRequest() {
        System.out.println("特殊请求!");
    }
}

public class Adapter extends Adaptee implements Target {

    public void request() {
       super.specificRequest();
       // 省略代码...
    }
}

针对这两种实现方式,在实际的开发中,到底该如何选择呢?判断的标准主要有两个,一个是 Adaptee 接口的个数,另一个是 Adaptee 和 Target 的契合程度。

  • 如果 Adaptee 接口并不多,那两种实现方式都可以。
  • 如果 Adaptee 接口很多,而且 Adaptee 和 Target 接口定义大部分都相同,那推荐使用类适配器,因为 Adapter 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
  • 如果 Adaptee 接口很多,而且 Adaptee 和 Target 接口定义大部分都不相同,那推荐使用对象适配器,因为组合结构相对于继承更加灵活。

适配器模式应用场景总结

一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。

那在实际的开发中,什么情况下使用适配器模式才比较合适呢?下面我们来看看它的应用场景吧。

1. 封装有缺陷的接口设计

如果我们依赖的外部系统在接口设计方面有缺陷(比如包含大量静态方法),引入之后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计,这个时候就可以使用适配器模式了。

比如下面这个例子:

// 这个类来自外部系统的
public class OtherManager {

public static void staticFunction1() {
    // 省略代码...
}
public void function2() {
    // 省略代码... 
}

// 使用适配器模式进行重构
public class Target {
    void function1();
    void function2();
    // ...
}

// 适配器
public class OtherManagerAdapter extends OtherManager implements Target {

    public void function1() {
      super.staticFunction1();
    }
    public void function2() {
      super.fucntion2();
    }

    //...
}

2. 替换依赖的外部系统

当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。具体的代码示例如下所示:

// 外部依赖 A
public class A {
    void functionA();
    // ...
}

public class AImpl implements A {
    public void functionA() {
      // ...
    }
}

// 使用外部依赖 A
public class Demo {
    public A a;

    public Demo(A a) {
      this.a = a;
    }
    // ...
}
Demo d = new Demo(new AImpl());


// 将外部依赖 A 换成外部依赖 B
public class BAdapter implements A {
    public B b;

    public BAdapter(B b) {
      this.b = b;
    }

    public void functionA() {
      // ...
      b.functionb();
    }
}

// 使用 BAdapter 替换 A
Demo d = new Demo(new BAdapter());

3. 统一多个类的接口设计

某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑。下面我们来看看这样一个例子。

假设我们的系统要对用户输入的文本内容做敏感词过滤,为了提高过滤的效果,我们引入了多款第三方敏感词过滤系统,依次对用户输入的内容进行过滤,过滤掉尽可能多的敏感词。但是,每个系统提供的过滤接口都是不同的,这就意味着我们没法复用一套逻辑来调用各个系统。这个时候,我们就可以使用适配器模式,将所有系统的接口适配为统一的接口定义,这样我们可以复用调用敏感词过滤的代码。

下面我们就使用适配器模式实现,具体代码如下:

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

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

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

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

    public String filterSensitiveWords(String text) {
        String maskedText = aFilter.filterWords(text);
        maskedText = bFilter.filter (maskedText);
        maskedText = cFilter.filter (maskedText,"***");

        return maskedText;
    }
}

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

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

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

    public void addSensi tiveWordsFilter (SensitiveWordsFilter filter) {
        filters. add(filter);
    }

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

总结

一般地,使用一个已经存在的类,但如果它的接口,也就是它的方法和要求不相同时,就可以考虑使用适配器模式。

其实,前面讲到的代理模式和适配器模式在结构上很相似,都需要一个具体的实现类的实例,只是它们的目的不一样;代理模式做的是增强或扩展原方法的事,而适配器做的只是适配的事,将一个现有方法适配成所需要的方法,是一种事后补救策略。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值