[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)

一、适配器模式

有些时候,我们撸码时会碰到这样的问题,需要将一个接口转变成另外的接口,让不兼容的接口变成兼容。而且使用这个“不兼容接口”的客户,不必为了应对不同的接口而跟着修改。

认识适配器模式:1、真是世界的适配器

如果你需要在欧洲国家使用美国制造的笔记本电脑,你可能需要使用一个交流电的适配器。


[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)_第1张图片
adapter_01.jpg

适配器的作用:它位于美式插头和欧式插座的中间,它的工作是将欧式插座转变成美式插座,好让美式插头可以插进这个插座得到电力。

认识适配器模式:2、面向对象适配器

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

[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)_第2张图片
adapter_02.jpg

不改变现有的代码,解决这个问题:写一个类,将新厂商的接口转换成你所期望的接口。

[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)_第3张图片
adapter_03.jpg

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


[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)_第4张图片
adapter_04.jpg
定义适配器模式

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

练习

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

①鸭子接口——Duck
public interface Duck {

    /**
     * 鸭子叫
     */
    void quack();

    /**
     * 鸭子飞
     */
    void fly();
}

具体的鸭子类——MallardDuck

public class MallardDuck implements Duck {
    @Override
    public void quack() {
        System.out.println("quack");
    }

    @Override
    public void fly() {
        System.out.println("i'm flying");
    }
}
②火鸡接口——Turkey
public interface Turkey {

    /**
     * 火鸡只会咯咯叫
     */
    void gobble();

    /**
     * 火鸡会飞,但是飞不远
     */
    void fly();
}

具体的火鸡类——WildTurkey

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");
    }
}
②火鸡适配器——TurkeyAdapter
public class TurkeyAdapter implements Duck {
    private Turkey mTurkey;

    public TurkeyAdapter(Turkey turkey) {
        mTurkey = turkey;
    }

    /**
     * 将火鸡的 咯咯叫 转换成 鸭子叫
     */
    @Override
    public void quack() {
        mTurkey.gobble();
    }

    /**
     * 将火鸡的飞行,转为 鸭子飞
     */
    @Override
    public void fly() {
        // 火鸡飞行距离很短,要让鸭子的飞行和火鸡的飞行能够对应,
        // 需要连续5次(只是模拟而已)调用火鸡的fly()来完成
        for (int i = 0; i < 5; i++) {
            mTurkey.fly();
        }
    }
}
④测试适配器
public class TestAdapter {

    public static void main(String args[]) {
        MallardDuck mallardDuck = new MallardDuck();//创建一只鸭子

        Turkey wildTurkey = new WildTurkey();//创建一只火鸡

        //将火鸡包装进一个火鸡适配器中,使它看起来像是一只鸭子
        TurkeyAdapter turkeyAdapter = new TurkeyAdapter(wildTurkey);

//        测试火鸡咯咯叫,让它飞行
        System.out.println("The Turkey says...");
        wildTurkey.gobble();
        wildTurkey.fly();

//        调用testDuck()方法来测试鸭子,这个方法需要传入一个鸭子对象
        System.out.println("\nThe Duck says...");
        testDuck(mallardDuck);

//        给testDuck()传入一个假装是 鸭子 的对象
        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

现在,我们知道,适配器模式可以通过创建适配器进行接口转换,让不兼容的接口变成兼容。这可以让客户从实现的接口解耦。如果在一段时间之后,我们想要改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的接口而每次跟着修改。

适配器模式类图:

[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)_第5张图片
adapter_05.jpg

这个适配器模式充满着良好的OO设计原则:使用对象组合,已修改的接口包装被适配者。这种做法还有个额外的有点,那就是,被适配者的任何子类,都可以搭配着适配器使用。

接下来转场外观模式。

二、外观模式

它将一个或者数个类的复杂的一切都隐藏在背后,只露出一个干净美好的外观——让接口更简单。

举例

在家庭影院中,准备看一部DVD影片。必须要执行一些任务:
1、打开爆米花机
2、开始爆米花
3、将灯光调暗
4、放下屏幕
5、打开投影仪
6、将投影仪设置在宽屏模式
7、打开功放
8、将功放的输入设置为DVD
9、将功放设置为环绕立体声
10、将功放音量调到中(5)
11、打开DVD播放器
12、开始播放DVD

代码如下

public class TestFacade {
    public static void main(String args[]) {
        Amplifier amplifier = new Amplifier();
        DvdPlayer dvdPlayer = new DvdPlayer();
        Projector projector = new Projector();
        Screen screen = new Screen();
        TheaterLights theaterLights = new TheaterLights();
        PopcornPopper popcornPopper = new PopcornPopper();

        System.out.println("Prepare watch DVD ....");
        popcornPopper.on();//打开爆米花机
        popcornPopper.pop();//开始爆米花
        theaterLights.dim(10);//将灯光调暗
        screen.down();//放下屏幕
        projector.on();//打开投影仪
        projector.wideScreenMode();//将投影仪设置在宽屏模式
        amplifier.on();//打开功放
        amplifier.setDvd(dvdPlayer);//将功放的输入设置为DVD
        amplifier.setSurroundSound();//将功放设置为环绕立体声
        amplifier.setVolume(5);//将功放音量调到中(5)
        dvdPlayer.on();//打开DVD播放器
        dvdPlayer.play("Raiders of the Lost Ark");//开始播放DVD
    }
}

看完电影后,还要把一切关掉
代码如下

public class TestFacade {
    public static void main(String args[]) {
        Amplifier amplifier = new Amplifier();
        DvdPlayer dvdPlayer = new DvdPlayer();
        Projector projector = new Projector();
        Screen screen = new Screen();
        TheaterLights theaterLights = new TheaterLights();
        PopcornPopper popcornPopper = new PopcornPopper();

        System.out.println("\n\nEnd movie....");
        popcornPopper.off();//关闭爆米花机
        theaterLights.on();//将灯光打开
        screen.up();//升起屏幕
        projector.off();//关闭投影仪
        amplifier.off();//关闭功放
        dvdPlayer.stop();//停止DVD播放
        dvdPlayer.eject();//弹出DVD
        dvdPlayer.off();//关闭DVD
    }
}

喔噢,看场电影不容易啊,好麻烦。

现在是时候将子系统的组件整合成一个统一的接口了。

引入外观模式

构造家庭影院外观——HomeTheaterFacade

public class HomeTheaterFacade {

    private Amplifier mAmp;//扩音器
    private DvdPlayer mDvd;//DVD播放器
    private Projector mProjector;//投影仪
    private TheaterLights mLights;//电灯
    private Screen mScreen;//屏幕
    private PopcornPopper mPopper;//爆米花

    public HomeTheaterFacade(Amplifier amp,DvdPlayer dvd, Projector projector, TheaterLights lights, Screen screen, PopcornPopper popper) {
        mAmp = amp;
        mDvd = dvd;
        mProjector = projector;
        mLights = lights;
        mScreen = screen;
        mPopper = popper;
    }

    public void watchMovie(String movie) {
        System.out.println("Get ready to watch a movie...");
        mPopper.on();//打开爆米花机
        mPopper.pop();//开始爆米花
        mLights.dim(10);//将灯光调暗
        mScreen.down();//放下屏幕
        mProjector.on();//打开投影仪
        mProjector.wideScreenMode();//将投影仪设置在宽屏模式
        mAmp.on();//打开功放
        mAmp.setDvd(mDvd);//将功放的输入设置为DVD
        mAmp.setSurroundSound();//将功放设置为环绕立体声
        mAmp.setVolume(5);//将功放音量调到中(5)
        mDvd.on();//打开DVD播放器
        mDvd.play(movie);//开始播放DVD
    }

    public void endMovie() {
        System.out.println("Shutting movie theater down...");
        mPopper.off();//关闭爆米花机
        mLights.on();//将灯光打开
        mScreen.up();//升起屏幕
        mProjector.off();//关闭投影仪
        mAmp.off();//关闭功放
        mDvd.stop();//停止DVD播放
        mDvd.eject();//弹出DVD
        mDvd.off();//关闭DVD
    }
}
测试
public class TestFacade {
    public static void main(String args[]) {
        Amplifier amplifier = new Amplifier();
        DvdPlayer dvdPlayer = new DvdPlayer();
        Projector projector = new Projector();
        Screen screen = new Screen();
        TheaterLights theaterLights = new TheaterLights();
        PopcornPopper popcornPopper = new PopcornPopper();
        HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(amplifier, dvdPlayer, projector, theaterLights, screen, popcornPopper);
        System.out.println("Prepare....");
        homeTheaterFacade.watchMovie("Raiders of the Lost Ark");
        System.out.println("\n\nEnd movie....");
        homeTheaterFacade.endMovie();
    }
}

结果

Prepare watch movie....
Get ready to watch a movie...
Popcorn Popper on
Popcorn Popper popping popcorn!
Theater Ceiling Lights dimming to 10%
Theater Screen going down
Top-O_Line Projector on
Top-O_Line Projector in wideScreen mode (16x9 aspect ratio)
Top-O_Line Amplifier on
Top-O_Line Amplifier setting DVD player to Top-O_Line DVD player
Top-O_Line Amplifier surround sound on (5 speaker,1 subwoofer)
Top-O_Line Amplifier setting volume to 5
Top-O_Line DVD player on
Top-O_Line DVD player playing "Raiders of the Lost Ark"


End movie....
Shutting movie theater down...
Popcorn Popper off
Theater Ceiling Lights on
Theater Screen going up
Top-O_Line Projector off
Top-O_Line Amplifier off
Top-O_Line DVD player stopped "Raiders of the Lost Ark"
Top-O_Line DVD player eject
Top-O_Line DVD player off

定义外观模式

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

外观模式的意图是提供一个简单的接口,好让一个子系统更易于使用,让客户和子系统之间避免紧耦合。

外观模式类图:
[设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade)_第6张图片
facade_01.jpg

外观模式涉及到一个新的原则

“最少知识”原则

只和你的密友谈话。

这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它需要花许多的成本维护,也会因为太复杂而不容易被其他人了解。

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

  • 该对象本身
  • 被当做方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件(把“组件”想象成是被实例变量所引用的任何对象,换句话说,把这想象成是“有一个”关系)。

请注意:这些方针告诉我们,如果某对象是调用其他的方法的放回结果,不要调用该对象的方法!

最少知识原则缺点:

虽然采用这个原则减少了对象之间的依赖,减少了维护成本,但是如果采用这个原则也会导致更多的“包装”类制造出来,以处理和其他组件的沟通,这可能会导致复杂度和开发时间的增加,并降低运行时的性能。

要点
  • 当需要使用一个现有的类而其接口并不符合你的需要时,就是用适配器。
  • 当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观。
  • 适配器改变接口以符合客户的期望。
  • 外观将客户从一个复杂的子系统中解耦。
  • 实现一个适配器可能需要一番功夫,也可能不费功夫,是目标接口的大小与复杂度而定。
  • 实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行。
  • 你可以为一个子系统实现一个以上的外观。
  • 适配器将一个对象包装起来以改变其接口,在装饰者模式中的装饰者将一个对象包装起来以增加新的行为和责任,而外观将一群对象“包装”起来以简化其接口。

感谢你的耐心阅读,适配器模式和外观模式基本知识和应用就介绍到这里了。

最后回顾一下

我们的设计工具箱中的工具
1、OO基础

① 抽象
② 封装
③ 多态
④ 继承

2、OO原则

① 封装变化
② 多用组合,少用继承
③ 针对接口编程,不针对实现编程
④ 为交互对象之间的松耦合设计而努力
⑤ 类应该对扩展开放,对修改关闭
⑥ 依赖抽象,不要依赖具体编程
⑦“最少知识”原则:只和朋友交谈

3、OO模式

① 策略模式——定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

② 观察者模式——在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知并自动更新。

③ 装饰者模式——动态地将责任附加到对象上,若要扩展功能,装饰者提供有别于继承的另一种选择。

④ 工厂方法模式——定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式——提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

⑤单件模式——确保一个类只有一个实例,并提供全局访问点。

⑥命令模式——将“请求”封装成对象,这可以让你使用不同的请求、队列或者日志来参数化其他对象。命令模式也可以支持撤销操作。

⑦适配器模式——将一个类的接口,转换成客户所期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
外观模式——提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

感谢阅读!
No.6 命令模式(Command)
No.5 单件模式(singleton )
No.4 工厂模式(Factory)
No.3 装饰者模式(Decorator)
No.2 观察者模式(Observer)
No.1 策略模式(Strategy)
前言 为何要使用设计模式

Demo代码

你可能感兴趣的:([设计模式笔记] No.7 适配器模式(Adapter)和外观模式(Facade))