适配器模式

适配器模式Adapter Pattern又称包装器模式Wrapper Pattern是作为两个接口或对象之间的桥梁,这种类型的设计模式属于结构型模式,它结合了两个独立接口或对象的功能,这种模式负责加入独立的或不兼容的接口与对象,在Js中通常可以使用适配器模式进行框架的适配、参数的适配以及数据的适配。

描述

在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式,通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用,如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器Adapter,它所包装的对象就是适配者Adaptee,即被适配的类。适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说,当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此适配器可以使由于接口不兼容而不能交互的类可以一起工作。

模式结构

  • Target: 目标类。
  • Adapter: 适配器类。
  • Adaptee: 适配者类。
  • Client: 客户类。

优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则。
  • 在类适配器模式中一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说同一个适配器可以把适配者类和它的子类都适配到目标接口。
  • 在对象适配器模式中由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

缺点

  • 对于JavaC#等不支持多重继承的语言,类适配器模式一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
  • 与类适配器模式相比,对象适配器模式要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用环境

  • 使用一个已经存在的对象,但其方法或属性接口不符合你的要求。
  • 要想创建一个可复用的对象,该对象可以与其它不相关的对象或不可见对象(即接口方法或属性不兼容的对象)协同工作。
  • 想使用已经存在的对象,但是不能对每一个都进行原型继承以匹配它的接口,对象适配器可以适配它的父对象接口方法或属性。

对比

  • 适配器和桥接模式虽然类似,但桥接的出发点不同,桥接的目的是将接口部分和实现部分分离,从而对他们可以更为容易也相对独立的加以改变。而适配器则意味着改变一个已有对象的接口。
  • 装饰者模式增强了其它对象的功能而同时又不改变它的接口,因此它对应程序的透明性比适配器要好,其结果是装饰者支持递归组合,而纯粹使用适配器则是不可能的。
  • 代理模式在不改变它的接口的条件下,为另外一个对象定义了一个代理。

实现

class Target {
    say(){
        console.log("Target say");
    }
}

class Adaptee {
    say(){
        console.log("Adaptee say");
    }
}

class Adapter extends Target {
    constructor(adaptee) {
        super();
        this.adaptee = adaptee;
    }
    say() {
        // Use this.adaptee to implement the method in target
        console.log("Adaptee to Target say")
    }
}

(function(){
    var adaptee = new Adaptee();
    var adapter=new Adapter(adaptee);
    adapter.say(); // Adaptee to Target say
})();

/**
    一个例子
    原有一个非常类似jQuery的轻量框架要转用jQuery,需要适配接口,假如只有通过id取得元素的方法需要适配
    class Target { // 这就是原轻量框架
        getElement(id){
            return document.getElementById(id);
        }
    }

    var adaptee = $; // 此处Adaptee即jQuery

    class Adapter extends Target {
        constructor(adaptee) {
            super();
            this.adaptee = adaptee;
        }
        getElement(id) {
            return this.adaptee(`#${id}`);
        }
    }
    
    // 之前是使用 Target 对象的实例 target
    // 适配之后使用 Adapter 对象的实例 adapter 并可以将 target 指向 adapter

 */

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://segmentfault.com/a/1190000012436538 https://www.runoob.com/design-pattern/adapter-pattern.html https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/adapter.html