设计模式之适配器模式和外观模式

来源:互联网 发布:业余时间挣钱 知乎 编辑:程序博客网 时间:2024/05/18 01:48

设计模式之适配器模式和外观模式


前言


先来讲个背景故事,来帮助理解适配器模式。假设,小明在香港读大学,在读大学期间,他在香港买了一台电脑,后来,回到内地读研,回来之后发现电脑的电源插头不能在内地使用,于是他在网上买了一个转接头,一头插上了自己电脑的电源适配器,另外一头就是能直接在内地的插座上使用,于是,他的电脑就可以正常充电使用了。

从上面的事例中,我们得到了一下几个信息:

  • 香港买的电脑的转接头无法直接在内地的插座上充电
  • 用户(小明)想在内地的插头上直接使用电源充电
  • 使用转接头之后就能实现香港的电源插头能直接在内地的插座直接使用

现在将上面的信息进行转换一下:

转接头能将不满足用户(小明)需求的接头转化为满足用户(小明)需求的接头

以上就是适配器的作用,其中转接头充当的就是适配器的角色,转接头实现了内地接头,同时需要组合引用港版电脑电源适配器插头。

实现这样的适配器模式


现在我们实现一个简单的适配器模式,假设有一个Duck(鸭子)类,还有一个Turkey(火鸡)类,我们知道,鸭子和火鸡不是同一个物种,现在的目的就是要让一只火鸡看起来像一只鸭子。

根据上面的需求,以及在前言中分析的信息,我们可以得到以下的信息:

  • 首先需要实现一个适配器(转接头)
  • 该适配器需要将一个火鸡类转化为一个鸭子类
  • 为了输出鸭子类,所以适配器需要实现鸭子接口
  • 为了接收输入的火鸡类,所以适配器需要组合引用鸭子接口

首先实现所需要的Duck类

根据之前的设计原则,面向接口编程,所以,我们需要先设计一个Duck的接口,该接口里面包含了两个方法:一个是鸭子会叫,另一个是鸭子会飞,根据这个需求设计以下的接口和实现类:

定义接口Duck

public interface Duck {    public void quack();    public void fly();}

实现一个Duck的具体类

public class MallardDuck implements Duck {    public void quack() {        System.out.println("MallardDuck quack");    }    public void fly() {        System.out.println("MallardDuck fly a long distance");    }}

此时就已经得到了一个鸭子的具体类,这个也是我们需要转化成的类型。接下来定义和实现Turkey的类。

然后实现所需要的Turkey类

跟上面一样,面向接口编程,所以,需要定义一个Turkey的接口,在该接口中需要实现两个方法,一个是火鸡也会叫,另一个是火鸡也会飞(但是注意和鸭子的飞是不一样的),根据这样的需求设计以下的接口和具体实现类:

定义Turkey接口

public interface Turkey {    public void gobble();    public void fly();}

实现具体的Turkey类

public class WildTurkey implements Turkey {    public void gobble() {        System.out.println("WildTurkey gobble");    }    public void fly() {        System.out.println("WildTurkey fly a short distance");    }}

此时得到了一个Turkey的接口,以及一个具体的实现类,这个是我们将要被转化的对象,最终就是通过适配器将Turkey类转化为Duck类。

Turkey转为Duck的适配器的实现

上面也进行了分析,我们需要做的就是实现一个适配器类TurkeyAdapter,这个类输出的是一个Duck类型,所以,这个适配器TurkeyAdapter要实现Duck的接口,同时,这个适配器类还需要组合被转化的类,因此,在这个适配类中还需要组合Turkey类型的类,所以,根据这个分析,我们基本已经可以设计出来这个适配器类TurkeyAdapter的雏形。

但是,我们该如何将Turkey的操作转化为Duck的操作了?我们可以看出来,只需要在实现鸭子会叫的方法中调用火鸡会叫的方法即可,但是鸭子比火鸡飞的快,那就让火鸡调用五次(其实随便几次)火鸡会飞的方法吧。这样就能实现用一个伪装的方式将一个Turkey类转化为一个Duck类了,一下就是具体的TurkeyAdapter类的实现:

public class TurkeyAdapter implements Duck {    Turkey turkey;    public TurkeyAdapter(Turkey turkey) {        this.turkey = turkey;    }    public void quack() {         turkey.gobble();    }    public void fly() {        for (int i = 0 ;i<5;i++) {            turkey.fly();        }    }}

以上就是一个完整的将Turkey转化为Duck的适配器,使用的时候只需要就传入一个Turkey类型的类,然后通过使用适配器的类,就可以像调用Duck类的方法一下进行使用。

适配器测试用例

以下是使用的示例:

public class TestTurkeyAdapter {    public static void main(String[] args) {        Turkey turkey = new WildTurkey();        Duck turkeyAdapter = new TurkeyAdapter(turkey);        turkeyAdapter.fly();        turkeyAdapter.quack();    }}

注意:

  • 通过TurkeyAdapter的构造器传入的是一个Turkey的对象
  • 然后TurkeyAdapter实例化之后的对象是Duck的类型
  • 接着使用TurkeyAdapter的实例化的对象是像Duck类的方法一样进行使用

这样,就完整的实现并且测试使用了TurkeyAdapter适配器,将Turkey类转为了Duck的对象。

使用总结和注意点

适配器的使用方法

  1. 定义适配器类实现需要转为的对象的抽象接口
  2. 在适配器类中组合引用被转化的对象的抽象接口
  3. 通过适配器类来指定组合引用的具体实现类的对象
  4. 通过使用组合对象的相关方法去实现需要被转为的类的相关方法

适配器的特点

  1. 适配器可以实现多个待输出的类型的抽象接口,这样在适配器中就需要将所有的接口中的方法都实现。
  2. 适配器可以分为类适配器和对象适配器,上面的实现就是对象适配器(实现+组合的方法),也是JAVA所支持的适配器的实现方法。类适配器是通过适配器继承两个类(一个是待转化,另一个是换化为)

适配器的扩展

实际的背景介绍

在早期的jdk中,集合类collection(如,Vector、HashTable)都实现了Enumeration接口,具有遍历元素的功能,但是现在的集合类实现的是Iterator接口,这个接口相对于Enumeration来说,除了具有判断元素是否有、获取元素之外,还有移除元素的功能。现在早期写的代码中,使用很多Enumeration接口,为了适应现在的需求,需要做一个适配器,将Enumeration转化为Iterator接口。

获得了上面的需求之后,需要分析一下,的到如下的信息:

  • 需要实现Iterable接口
  • 需要组合引入Enumearation类型的对象
  • Enumeration对象中没有remove一个元素的功能

针对第三个问题,在将Enumeration转为Iterable的过程中,有一些方法没有提供,此时可以在方法中以抛出异常的形式进行处理:UnsupportedOperationException

代码实现

实现EnumerationToIterable

public class EnumerationToIterable implements Iterator{    Enumeration enumeration;    EnumerationToIterable(Enumeration enumeration) {        this.enumeration = enumeration;    }    public boolean hasNext() {        return enumeration.hasMoreElements();    }    public Object next() {        return enumeration.nextElement();    }    public void remove(){        throw new UnsupportedOperationException("remove operation not support");    }}

同样的,为了能让以前的代码中保持统一的使用Enumeration的类型,就需要将现在的Iterator接口类型转化为Enumeration接口,所以,分析一下,跟上面实现方法类似。

实现IteratorEnumeration类型

public class IteratorEnumeration<T> implements Enumeration<T>{    Iterator<T> iterator;    public IteratorEnumeration(Iterable<T> iterable){        this.iterator = iterable.iterator();    }    public boolean hasMoreElements() {        return iterator.hasNext();    }    public T nextElement() {        return iterator.next();    }}

以上便是实现了将一个Iterator接口转为Enumeration接口,这样就能在旧代码中继续使用Enumeration接口了。注意,有一个点,看构造器里面,传入的是Iterable接口,而不是Iterator,原因是在Iterable中又组合了Iterator类型,所以,使用如何给iterator进行赋值:

this.iterator = iterable.iterator();

那么怎么来测试一下这个适配器呢?

TestIteratorEnumeration来测试IteratorEnumeration接口

首先,需要传入一个实现了Iterator接口的类,但是从jdk的源码中可以看出,例如ArrayList这个类,它所实现的就是Iterable接口,但是里面组合了Iterator,所以可以选择使用ArrayList来传入给IteratorEnumeration类,然后,返回一个Enumeration接口,然后就像使用Enumeration一样使用即可,如下所示:

public class TestIteratorEnumeration {    public static void main(String[] args) {        ArrayList<String> list = new ArrayList<String>();        list.add("Hello");        list.add("Adapter");        Enumeration<String> enumeration = new IteratorEnumeration<String>(list);        while (enumeration.hasMoreElements()) {            System.out.println(enumeration.nextElement());        }    }}

这个时候可以正常使用Enumeration接口的方法将ArrayList中的元素打印出来。

外观模式

背景

假设,Jack准备在家安装一套影院体验的全套设备,他将所有的设备(巨幕、音箱、效果灯、爆米花机等等)都买回来了,并且都安装好了,接下来就是开始欣赏一部电影了。于是,他需要做:

  1. 打开DV
  2. 打开投影
  3. 调节灯光
  4. 打开爆米花机
  5. 。。。

    看到这儿,就知道我要说什么,想想要做的事就很多,那么能不能简单一点?

    将一个复杂的系统里面的多个小子系统进行封装,提供一个高层的接口,使得子接口更易于使用

简单介绍和实现

例如,如下的方式实现了开启影院的模式按钮开关:

public void openCinimer() {    openDV();    openReflect();    setLight();    startMechine();    ...}

这样的话就可以更加简单的使用这些子系统中的功能了。

设计原则

最少知识原则:只和最亲密的朋友进行交谈

原创粉丝点击