设计模式之适配器模式

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

面向对象适配器

假设已有一个软件系统,你希望它能和一个新的产商类库搭配使用,但是这个新产商所设计出来的接口,不同于旧产商的接口:
1012917-20200121215900401-1144552570.png
你不想改变现有的代码,解决这个问题(而且你也不能改变产商的代码)。所以该怎么做?你可以写一个类,将新产商接口转换成你所期望的接口。


这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换成产商类能理解的请求。这个适配器实现了你的类所期望的接口,而且这个适配器也能和产商的接口沟通。

火鸡转换器

鸭子和火鸡的接口和类

// 鸭子基类
public interface Duck {
    public void quack();
    public void fly();
}

// 绿头鸭是鸭子的子类,实现了鸭子的呱呱叫和飞行的能力
public class MallardDuck implements Duck{
    @Override
    public void quack() {
        System.out.println("Quack");
    }

    @Override
    public void fly() {
        System.out.println("I'm flying");
    }
}

// 火鸡基类
public interface Turkey {
    // 火鸡不会呱呱叫,只会咯咯叫
    public void gobble();
    // 火鸡会飞,虽然飞不远
    public void fly();
}

// 野生火鸡
public class WildTurkey implements Turkey{
    @Override
    public void gobble() {
        System.out.println("Gobble gobble");
    }

    @Override
    public void fly() {
        System.out.println("I'm flying a short distance");
    }
}

适配器类

现在,假设你缺鸭子对象,想用一些火鸡对象来冒充。显而易见,因为火鸡的接口不同,所以我们不能公然拿来用。那么,写个适配器吧:

// 首先,你需要实现想转换成的类型接口,也就是你的客户期望看到的接口
public class TurkeyAdapter implements Duck {
    Turkey turkey;

    // 接着,需要取得要适配的对象引用,这里我们引用构造器取得这个引用
    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    // 现在我们需要实现接口中所有的方法。quack()在类之间的转换很简单,
    // 只要调用gobble()接可以了
    @Override
    public void quack() {
        turkey.gobble();
    }

    // 固然两个接口都具备了fly()方法,火鸡的飞行距离很短,不像鸭子可以长途飞行。
    // 要让鸭子的飞行和火鸡的飞行能够对应,必须连续五次调用火鸡的fly()来完成
    @Override
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}

测试

public class DuckTestDrive {
    public static void main(String[] args) {
        MallardDuck duck = new MallardDuck();

        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);

        System.out.println("The Turkey says...");
        turkey.gobble();
        turkey.fly();

        System.out.println("\nThe Duck says...");
        testDuck(duck);

        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }

}

输出: 
    The Turkey says...
    Gobble gobble
    I'm flying a short distance

    The Duck says...
    Quack
    I'm flying

    The TurkeyAdapter says...
    Gobble gobble
    I'm flying a short distance
    I'm flying a short distance
    I'm flying a short distance
    I'm flying a short distance
    I'm flying a short distance



适配器模式解析

image.png

客户使用适配器的过程如下:

  1. 客户通过目标接口调用适配器的方法对适配器发出请求;
  2. 适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口;
  3. 客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。

请注意,客户和被适配者是解耦的,一个不知道另一个。

image.png

现在,我们知道,这个模式可以通过创建适配器进行接口转换,让不兼容的接口变成兼容。这可以让客户从实现的接口解耦。如果在一段时间之后,我们想要改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。
这个适配器模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者。这种做法还有额外的优点,那就是,被适配者的任何子类,都可以搭配着适配器使用。
也请留意,这个模式是如何把客户和接口绑定起来,而不是和实现绑定起来的。我们可以使用数个适配器,每一个都负责转换不同组的后台类。或者,也可以加上新的实现,只要它们遵守目标接口就可以。

对象适配器和类适配器

类适配器通过多重继承来实现,而对象适配器利用组合的方式将请求传递给被适配者。

image.png
image.png

枚举类适配到迭代器

image.png
image.png

// 因为我们将枚举适配成迭代器.适配器需粟实现迭代器接口。适配器必须看起来就像是一个迭代器。
public class EnumerationIterator implements Iterator {
    Enumeration enumeration;

    // 组合方式:将枚举结合进适配器
    public EnumerationIterator(Enumeration enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {
        return enumeration.hasMoreElements();
    }

    @Override
    public Object next() {
        return enumeration.nextElement();
    }

    // 枚举类不能支持remove方法
    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

}

对比

模式 意图
适配器 将一个接转成另一个接口
装饰者 不改变接口,但加入责任
外观 让接口更简单
posted @ 2021-12-22 20:45  追梦少年阿飞  阅读(133)  评论(0)    收藏  举报