适配器模式与外观模式

适配器GitHub代码
外观GitHub代码

适配器模式

场景描述:想象一下我们维护了一套老系统,并于一个厂商通过接口进行交互。但是最近厂商更新它们的代码,并变更了接口格式,我们如何在不修改老系统代码的基础上,优雅的过渡呢?

提出方案:我们可以想象一下我们平时使用的转换头,这就是一种适配器。我们可以通过这种方式来优雅的“将方形放入圆形中”。

我们尝试举一个例子,假设现在有一个鸭子接口

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;
    }

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

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

可以通过结果看到我们现在将这个火鸡作为一只鸭子的实现了。
适配器模式与外观模式

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

我们看一下它的类图
适配器模式与外观模式
实现一个适配器所需要进行的工作,和目标接口的大小成正比。

由于Java不支持多继承,所以适配器又分为“对象”适配器(Java)和“类”适配器(支持多继承的其他语言)。

再看一下“类”适配器的类图
适配器模式与外观模式

外观模式

场景描述:我们现在有一个家庭影院,其中包含了DVD,投影机,自动屏幕,立体环绕声等等,每当我们想要看电影的时候,都需要进行一系列操作;而看完电影时,又需要反向再操作一遍。

改进方案:我们尝试进行一种近似流水线的改造,通过将一系列操作变成一个更加高层的操作。

如下是我们的改进方案的代码

public class HomeTheaterFacade {
    Amplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;

    public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, TheaterLights lights, Screen screen, PopcornPopper popper){
        this.amp = amp;
        this.tuner = tuner;
        this.dvd = dvd;
        this.cd = cd;
        this.projector = projector;
        this.lights = lights;
        this.screen = screen;
        this.popper = popper;
    }

    public void watchMovie(String movie){
        System.out.println("Get ready to watch a movie...");
        popper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setDvd();
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);
    }

    public void endMovie(){
        System.out.println("Shutting movie theater down...");
        popper.off();
        lights.on();
        screen.up();
        projector.off();
        amp.off();
        dvd.stop();
        dvd.eject();
        dvd.off();
    }

这样我们测试一下,只可以通过一步操作就完成之前的一系列操作

public class HomeTheaterTestDrive {
    public static void main(String[] args) {
        Amplifier amp = new Amplifier();
        Tuner tuner = new Tuner();
        DvdPlayer dvd = new DvdPlayer();
        CdPlayer cd = new CdPlayer();
        Projector projector = new Projector();
        Screen screen = new Screen();
        TheaterLights lights = new TheaterLights();
        PopcornPopper popper = new PopcornPopper();

        HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, lights, screen, popper);

        homeTheater.watchMovie("Raiders of the Lost Ark");
        System.out.println("\n-----------Watching Movies------------\n");
        homeTheater.endMovie();
    }
}

结果如下
适配器模式与外观模式

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

外观模式有一个很好的特征:提供简化的接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。
以下是它的类图
适配器模式与外观模式
适配器模式的意图是,“改变”接口符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。
我们将装饰者模式、适配器模式、外观模式一同比较,可以看出它们的意图分别为:

  • 装饰者模式,不改变接口,但加入职责
  • 适配器模式,将一个接口转成另一个接口
  • 外观模式,让接口更简单

设计原则:最少知识原则:只和你的密友谈话。

当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。
这个原则又名墨忒耳法则,它希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。

这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

  • 该对象本身
  • 被当作方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件

如果调用从另一个调用中返回的对象的方法,会有什么害处呢?如果我们这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。
适配器模式与外观模式
这个原则的缺点是,采用这个原则会导致更多的“包装”类被制造出来,以处理和其他组件的沟通,这可能会导致复杂度和开发时间的增加,并降低运行时的性能。

总结如下:当我们需要从一个接口“转变”为另一个接口时,可以尝试使用适配器;而当我们想要将子系统的一系列操作简化时,可以使用外观模式。

上一篇:矩阵分解


下一篇:集成算法(随机森林)【知识整理】