设计模式之适配器模式

假设姚明刚到火箭队的时候,由于语言不通,但是有需要接受教练的指挥。有以下三种办法:

  • 让姚明先学会英语
  • 让教练学会中文指挥
  • 给姚明配一个翻译

显然,最具有可行性的方式便是给姚明配一个翻译。

我们简单的模拟一下上面的情景模式:

//姚明类,刚开始只会接受中文指挥


public class YaoMing { public void chineseOffensive(){ System.out.println("发起进攻"); } public void chineseDefensive(){ System.out.println("进行防守"); } }

//NBA队员接口

public interface NBAPlayer {

    void englishOffensive();

    void englishDefensive();
}

//NBA队员其中一个实现

public class PlayerOne implements NBAPlayer {

    @Override
    public void englishOffensive() {
        System.out.println("go go go");
    }

    @Override
    public void englishDefensive() {
        System.out.println("no no no");
    }
}

//主要使用,没有翻译的情况下

public class Main {
    public static void main(String[] args) {
        NBAPlayer nbaPlayer=new PlayerOne();
        YaoMing yaoMing = new YaoMing();

        nbaPlayer.englishDefensive();
        yaoMing.chineseOffensive();
    }
}

可以看见,在没有翻译的情况下,如果要同时使用YaoMing类和NBAPlayer,那么使用者需要兼容两类方法,然而在实际使用中,由开闭原则我们知道,不应该去修改原本工作正常的代码,这个时候我们可以定义一个适配类

public class NBAPlayerAdapter implements NBAPlayer {

    private final YaoMing yaoMing;

    public NBAPlayerAdapter(YaoMing yaoMing) {
        this.yaoMing = yaoMing;
    }

    @Override
    public void englishOffensive() {
        yaoMing.chineseOffensive();
    }

    @Override
    public void englishDefensive() {
        yaoMing.chineseDefensive();
    }
}

适配类内部包含YaoMing对象,外部实现NBAPlayer接口,当使用者需要使用YaoMing的时候,只用传达给适配器,再由适配器负责传达给YaoMing即可

    public static void main(String[] args) {
        YaoMing yaoMing=new YaoMing();

        NBAPlayer nbaPlayer=new NBAPlayerAdapter(yaoMing);
        nbaPlayer.englishDefensive();
    }

以上示例代码均在:https://github.com/dengchengchao/design-pattern

定义:

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。

适配器模式属于结构型设计模式

UML:

image

  • Target:目标抽象类
  • Adapter: 适配器类
  • Adaptee: 适配者类
  • Client: 客户端类

在四人帮中的适配器定义中,适配器分为两类:类适配器模式和对象适配器模式,但是由于C#,Java等语言不支持多重继承,因此一般用的到只有对象适配器(也就是初始化的时候传入一个被适配的对象)

应用场景:

  • 当想要使用现在有的类,而这些类的接口不符合系统的需求的时候
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

适配器模式一般来说在各个框架中都会存在,因为一般来说框架都会调用客户端的代码,而对于客户端的代码,又有很大的不确定性,因此此时可以使用适配器模式进行过度。例如:

  • Tomcat中的CoyoteAdapter用来统一适配不同的网络IO框架生成统一的Request
  • Spring MVCDispatcherServlet调用处理请求的HandlerAdapter,用来屏蔽不同的Handler的具体实现方式不同所带来的差异

优点:

  • 更好的复用性
    系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

  • 透明、简单
    客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单 & 更直接

  • 更好的扩展性
    在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

  • 解耦性
    将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码

  • 符合开放-关闭原则
    同一个适配器可以把适配者类和它的子类都适配到目标接口;可以为不同的目标接口实现不同的适配器,而不需要修改待适配类

缺点

  • 过多的使用适配器,会让系统非常零乱,不易整体进行把握

适配器和外观模式的区别:在于他们的意图。适配器模式的意图是:将接口转换成不同接口,外观模式的意图是提供子系统的一个简化接口。(head first 设计模式)