关于设计模式:一文彻底弄懂适配器模式Adapter-Pattern

50次阅读

共计 5777 个字符,预计需要花费 15 分钟才能阅读完成。

回到博客导航

设计用意

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它联合了两个独立接口的性能。

在某些时候,客户冀望取得某种性能接口但现有的接口无奈满足客户的需要,例如美国的失常供电电压为 110V,一个中国人带了一款中国制作电器去美国,这个电器必须要在 220V 电压下能力充电应用。这种状况下,客户 (中国人) 的冀望接口是 有一个 220V 的电压为电器充电 ,但理论的接口是 仅有一个 110V 的电压供电器充电 ,这种状况下就须要采纳一根电压转换器(适配器) 使得 110V 的电压可能转换为 220V 的电压,供客户应用。

将一个类的接口转换成客户心愿的另外一个接口,这就是适配器须要做的事件,适配器模式使得本来因为接口不兼容而不能一起工作的那些类能够一起工作。

实用条件

  • 零碎须要应用现有的类,而此类的接口不合乎零碎的须要(外围需要)。
  • 想要建设一个能够重复使用的适配器类,用于与一些彼此之间没有太大关联的一些类,包含一些可能在未来引进的类一起工作,这些源类不肯定有统一的接口,但通过适配器使得它们都具备统一的接口。
  • 通过接口转换,将一个类插入另一个类系中。(比方老虎和走兽,当初多了一个飞虎,在不减少实体的需要下,减少一个适配器,在外面容纳一个虎对象,实现飞的接口。)

设计

通常有两种形式实现适配器模式,一种是类适配器,类适配器目前已不太应用,另一种实现形式是对象适配器,通常状况下采纳对象适配器会使得代码更易扩大与保护。

不论采纳何种形式,其根本的实现思维都是:对现有接口的实现类进行扩大,使其实现客户冀望的指标接口。

类适配器通过继承现有接口类并实现目标接口,这样的话会使得现有接口类齐全对适配器裸露,使得适配器具备现有接口类的全副性能,毁坏了封装性。此外从逻辑上来说,这也是不合乎常理的,适配器要做的是扩大现有接口类的性能而不是代替,类适配器只有在特定条件下会被应用。

对象适配器持有现有接口类一个实例,并扩大其性能,实现目标接口。这是举荐的形式,优先采纳组合而不是继承,会使得代码更利于保护。此外,这也是十分合乎常理的——“给我一根线,让我来给他加长到 5m,我并不需要晓得这跟线是什么组成的,因为我的工作就是让线加长到 5m”——咱们扩大了相应性能而并不关怀其具体实现。

类适配器结构图:

对象适配器结构图:

  • Target:客户冀望取得的性能接口(220V 电压供电)。
  • Cilent:客户,冀望拜访 Target 接口(客户冀望能有 220V 电压)。
  • Adaptee:现有接口,这个接口须要被适配(现有 110V 电压供电,须要被适配至 220V)。
  • Adapter:适配器类,适配现有接口使其合乎客户需要接口(适配 110V 电压,使其变为 220V 电压)。

在适配器模式中,Cilent 调用 Adapter 以取得相应性能,Adapter 扩大 Adaptee 以实现对应性能。

代码示例

类适配器:

// 客户冀望的接口——220V 的电压充电
interface Target {void chargeBy220V();
}

// 现有接口——只能通过 110V 电压充电
interface Adaptee {void chargeBy110V();
}

// 现有接口的具体实现类,美国供电器——通过 110V 电压供电
class americanCharger implements Adaptee {
    @Override
    public void chargeBy110V() {System.out.println("美国供电器,只为你服务,正在通过 110V 电压为您充电");
    }
}

// 类适配器,通过继承现有接口来实现对现有接口的扩大
class Adapter extends americanCharger implements Target {
    @Override
    public void chargeBy220V() {super.chargeBy110V();// 现有性能
        System.out.println("再加 110V,达到 220V,冲鸭!");// 对现有性能扩大
    }
}

// 测试类
public class Test {public static void main(String[] args) throws FileNotFoundException {
        // 类适配器使得代码逻辑凌乱
        // 这种状况下好像 Adapter 是一种 110V 的美国供电器能够间接应用不须要其余信息
        // 具体能够和对象适配器比照以下
        new Adapter().chargeBy220V();
    }
}

// 输入
/*
美国供电器,只为你服务,正在通过 110V 电压为您充电
再加 110V,达到 220V,冲鸭!*/

对象适配器:

// 客户冀望的接口——220V 的电压充电
interface Target {void chargeBy220V();
}

// 现有接口——只能通过 110V 电压充电
interface Adaptee {void chargeBy110V();
}

// 现有接口的具体实现类,美国供电器——通过 110V 电压供电
class americanCharger implements Adaptee {
    @Override
    public void chargeBy110V() {System.out.println("美国供电器,只为你服务,正在通过 110V 电压为您充电");
    }
}

// 类适配器,通过继承现有接口来实现对现有接口的扩大,使得可能 110V 供电
class Adapter implements Target {
    Adaptee adaptee;// 持有现有接口具体实现对象的援用

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

    @Override
    public void chargeBy220V() {adaptee.chargeBy110V();// 该对象的现有性能
        System.out.println("再加 110V,达到 220V,冲鸭!");// 对现有性能扩大
    }
}

// 测试类
public class Test {public static void main(String[] args) throws FileNotFoundException {
        // 当初咱们有一个美国 110V 供电站,但咱们无奈应用
        Adaptee adaptee = new americanCharger();

        // 咱们将这个供电器交给适配器,适配器转换为 220V 供电器
        Adapter adapter = new Adapter(adaptee);

        // 接下来咱们通过适配器充电就好了
        adapter.chargeBy220V();}
}
// 输入同上

对象适配器采纳组合的形式实现对现有接口的扩大以达到客户冀望的接口。

让咱们来看 JavaIO 流中的一个实例:

FileInputStream fis = new FileInputStream("qe");
InputStreamReader isrAdapter = new InputStreamReader(fis);
BufferedReader bf = new BufferedReader(isrAdapter);

BufferedReader(此处为客户)须要读取文件字符流进行工作,读取文件字符流就是客户的需要局部,然而依据现有的接口,想要读取文件就只能读取字节流,FileInputStream 就是现有接口的一个具体实现类,为了满足客户的需要,咱们要对现有的接口进行适配,InputStreamReader 就是一个适配器,它持有一个现有接口类的实例,通过这个实例读取文件字节流并将其扩大为字符流以满足客户的需要,这是规范的对象适配器模式。如果认真钻研源码,发现 JavaIO 库将适配器定义为形象的,并由具体的适配器继承该形象适配器,如这里的 InputStreamReader 就是具体的适配器之一。

如果实现适配有多种形式的话,咱们能够将适配器类 Adapter 申明为抽象类,并由子类扩大它:

// 客户冀望的接口——220V 的电压充电
interface Target {void chargeBy220V();
}

// 现有接口——只能通过 110V 电压充电
interface Adaptee {void chargeBy110V();
}

// 现有接口的具体实现类,美国供电器——通过 110V 电压供电
class americanCharger implements Adaptee {
    @Override
    public void chargeBy110V() {System.out.println("美国供电器,只为你服务,正在通过 110V 电压为您充电");
    }
}

// 抽象类适配器,通过继承现有接口来实现对现有接口的扩大
abstract class Adapter implements Target {
    Adaptee adaptee;// 持有现有接口具体实现对象的援用

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

// 中国自制
class ChinaMakeAdapter extends Adapter {public ChinaMakeAdapter(Adaptee adaptee) {super(adaptee);
    }

    @Override
    public void chargeBy220V() {adaptee.chargeBy110V();// 该对象的现有性能
        System.out.println("再加 110V,达到 220V,认准中国制作,冲鸭!");// 对现有性能扩大
    }
}

// 测试类
public class Test {public static void main(String[] args) throws FileNotFoundException {
        // 当初咱们有一个美国 110V 供电站,但咱们无奈应用
        Adaptee adaptee = new americanCharger();

        // 咱们将这个供电站交给中国制作的适配器
        Adapter adapter = new ChinaMakeAdapter(adaptee);

        // 接下来咱们通过适配器充电就好了
        adapter.chargeBy220V();}
}
// 输入同上

此外能够适配器还通过实现两个接口以达到双向适配的目标,即从接口 A 能够适配到接口 B,从接口 B 也能够适配到接口 A,这种状况并不常见。

// 接口 A——220V 的电压供电
interface A {void chargeBy220V();
}


// 接口 A 的具体实现类,中国供电器——通过 220V 电压供电
class ChinaCharger implements A {
    @Override
    public void chargeBy220V() {System.out.println("220V 电压中国充电,值得信赖");
    }
}

// 接口 B——110V 电压供电
interface B {void chargeBy110V();
}

// 接口 B 的具体实现类,美国供电器——通过 110V 电压供电
class AmericanCharger implements B {
    @Override
    public void chargeBy110V() {System.out.println("美国充电器,只为你服务,正在通过 110V 电压为您充电");
    }
}

// 双向适配器
class Adapter implements A, B {
    A a; //220V 充电
    B b; //110V 充电


    public Adapter(A a) {this.a = a;}

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

    @Override
    public void chargeBy220V() {b.chargeBy110V(); // 以后接口
        System.out.println("加码,加到 220V!!");// 适配指标接口
    }

    @Override
    public void chargeBy110V() {a.chargeBy220V();// 以后接口
        System.out.println("缓冲电压,当初是 110V 了");
    }
}


// 测试类
public class Test {public static void main(String[] args) throws FileNotFoundException {
        // 咱们去美国,酒店里有一个美国 110V 充电站,咱们须要 220V 的电压
        B b = new AmericanCharger();
        // 咱们将这个充电站交给适配器以获取 220V 电压充电
        Adapter adapter1 = new Adapter(b);
        // 接下来咱们通过适配器充电就好了
        adapter1.chargeBy220V();

        System.out.println();

        // 美国人来中国,酒店里有一个中国 220V 充电站,但他须要 110V 的电压
        A a = new ChinaCharger();
        // 将这个充电站交给适配器以获取 110V 电压充电
        Adapter adapter2 = new Adapter(a);
        // 接下来咱们通过适配器充电就好了
        adapter2.chargeBy110V();}
}
// 输入
/*
美国充电器,只为你服务,正在通过 110V 电压为您充电
加码,加到 220V!!220V 电压中国充电,值得信赖
缓冲电压,当初是 110V 了

Process finished with exit code 0
*/

通过实现两个接口的形式,达到不同接口的双向适配,在某些状况下还是很实用的,例如 TypeC—USB 接口转换器,既能从 typeC 转 USB 也能从 USB 转 typeC。

适配器模式总结

长处:

  1. 能够让任何两个没有关联的类一起运行。
  2. 进步了类的复用,能够统一化多个不同接口。
  3. 将现有接口实现类暗藏,减少了类的透明度。
  4. 灵活性高,可自在适配。

毛病:

  1. 过多地应用适配器,会让零碎十分零乱,不易整体进行把握。比方,明明看到调用的是 A 接口,其实外部被适配成了 B 接口的实现,一个零碎如果太多呈现这种状况,无异于一场劫难。因而如果不是很有必要,能够不应用适配器,而是间接对系统进行重构。
  2. 某些适配工作可能十分艰难,例如让房子飞起来。

当咱们有动机地批改一个失常运行的零碎的接口,这时应该思考应用适配器模式。

注意事项:适配器不是在具体设计时增加的,而是解决正在退役的我的项目的问题,即现有接口可能无奈扭转(去美国不可能把人家 110V 电压供应改成 220V 电压供应)。

正文完
 0