适配器模式

概念

适配器的作用:它位于美式插头和欧式插座的中间,它的工作是将欧式插座转换成美式插座,好让美式插头可以插进这个插座得到电力。或者也可以这么认为:适配器改变了插座的接口,以符合美式笔记本电脑的需求。

某些交流电适配器相当简单,它们只是改变插座的形状来匹配你的插头,直接把电流传送过去。但是有些适配器内部则是相当复杂,可能会发改电流符合装置的需求。

OO适配器和真实世界的适配器扮演着同样的角色:将一个接口转换成另一个接口,以符全客户的期望

假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新厂商所设计出来的接口,不同于旧厂商的接口:

你不想改变现有的代码,解决这个问题(而且你也不能改变厂商的代码)。所以该怎么做?这个嘛,你可以写一个类,将新厂商接口转接成你所期望的接口。

这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换成厂商类能理解的请求。

示例

火鸡转接器

如果它走起路来像只鸭子,叫起来像只鸭子,那么他必定可能是一只鸭子包装了鸭子适配器的火鸡......

鸭子接口

public interface Duck {
    void quack();

    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 {
    void gobble();

    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 {

    private Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    @Override
    public void quack() {
        turkey.gobble();
    }

    @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 say...");
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }
}
/*output:
The Turkey says....
Gobble gobble
I'm flying a short distance

The Duck says...
Quack
I'm flying

The TurkeyAdapter say...
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
*/

类图

真实世界的适配器

让我们看看真实世界中一个简单的适配器(至少比鸭子更实际些)......

旧世界的枚举器

如果你已经使用这Java,可能记得早期的集合(collection)类型(例如:Vector, Stack, Hashtable)都实现了一个名为elements()的方法。该方法会返回一个Enumeration(举)。这个Enumeration接口可以走过此集合内的每个元素,而无需知道它们集合内是如何被管理的。

新世界的迭代器

当Sun推出更新的集合类时,开始使用Iterator(迭代器)接口,这个接品和枚举接口很像,都可以让你遍历此集合类型内的每个元素,但不同的是,迭代器还提供了删除元素的能力。

将枚举适配到迭代器

我们先看看这两个接口,找出它们的方法映射关系。换句话说,我们要找出每个适配器方法在被适配者中的对应方法是什么

设计适配器

这个类应该是这样的:我们需要一个适配器,实现目标接口,而此目标妆口是由被适配者所组合的。hasNext()和next()方法很容易实现,直接把它们从目标对应到被适配者就可以了。但是对于remove()方法,我们又该怎么办?请花一些时间想一想(我们在下一页就会处理)。目前,类图是这样的:

处理remove()方法

好了,我们知道枚举不支持删除,因为枚举是一个“只读”接口。适配器无法实现一个有实际功能的remove()方法,最多只能抛出一个运行地异常。幸运地,迭代器接口的设计者事先料到了这样的需要,所以将remove()方法定义成会抛出UnsupportedOperationException。

在这个例子中,我们看到适配器并不完美;客户必须小心潜在的异常,但只要客户小心,而且适配器的文档能做出说明,这也算是一个合理的解决方案。

编写一个EnumeratorIterator适配器

这是一份简单而有效的代码,适合依然会产生枚举的遗留类。

results matching ""

    No results matching ""