外观模式

1 问题提出

现在我们要组建一个家庭影院,家庭影院包括:媒体播放器、投影仪、灯光、升降式屏幕、环绕立体声、爆米花机。每个设备都有遥控器可以控制打开和关闭,我们应该如何实现才能让整个过程简单且清晰呢?
通常的方案是:将每个设备用一个类来表示,然后客户端调用各个类的实例来完成整个过程,这种方式虽然可以实现功能,但是客户端使用起来很不方便,如果能将每个设备的功能再做一层封装,对客户端只暴露特定接口,就可以简化客户端的操作复杂度,这就是外观模式。


客户端与各个设备直接交互

2 外观模式

外观模式也叫做过程模式,外观模式未子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需要和这个接口发生调用,而无需关心这个子系统的内部细节。
角色分析:
1)外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当的子系统。
2)调用者(Client):外观类的使用者。
3)子系统集合:处理外观类指派的任务,它是功能的实际提供者。


外观模式的类图

3 外观模式解决影院管理问题

首先将所有的子系统(设备)的类实现出来:
媒体播放器(MediaPlayer):

public class MediaPlayer {
    private static final MediaPlayer INSTANCE = new MediaPlayer();

    private MediaPlayer() {
    }

    public static MediaPlayer getInstance() {
        return INSTANCE;
    }

    public void turnOn() {
        System.out.println("打开电影播放器");
    }

    public void turnOff() {
        System.out.println("关闭电影播放器");
    }

    public void play() {
        System.out.println("正在播放电影");
    }

    public void pause() {
        System.out.println("暂停播放电影");
    }
}

爆米花机(Popcorn):

public class Popcorn {
    private static final Popcorn INSTANCE = new Popcorn();

    private Popcorn() {
    }

    public static Popcorn getInstance() {
        return INSTANCE;
    }

    public void turnOn() {
        System.out.println("打开爆米花机");
    }

    public void turnOff() {
        System.out.println("关闭爆米花机");
    }

    public void pop() {
        System.out.println("正在出爆米花");
    }
}

立体环绕音(Stereo):

public class Stereo {
    private static final Stereo INSTANCE = new Stereo();

    private Stereo() {
    }

    public static Stereo getInstance() {
        return INSTANCE;
    }

    public void turnOn() {
        System.out.println("打开立体声");
    }

    public void turnOff() {
        System.out.println("关闭立体声");
    }

    public void upVoice() {
        System.out.println("调高音量");
    }

    public void downVoice() {
        System.out.println("调低音量");
    }
}

升降式屏幕(Screen):

public class Screen {
    private static final Screen INSTANCE = new Screen();

    private Screen() {
    }

    public static Screen getInstance() {
        return INSTANCE;
    }

    public void up() {
        System.out.println("升起屏幕");
    }

    public void down() {
        System.out.println("下降屏幕");
    }
}

投影仪(Projector):

public class Projector {
    private static final Projector INSTANCE = new Projector();

    private Projector() {
    }

    public static Projector getInstance() {
        return INSTANCE;
    }

    public void turnOn() {
        System.out.println("打开投影仪");
    }

    public void turnOff() {
        System.out.println("关闭投影仪");
    }

    public void focus() {
        System.out.println("投影仪正在聚焦");
    }
}

灯光(TheaterLight):

public class TheaterLight {
    private static final TheaterLight INSTANCE = new TheaterLight();

    private TheaterLight() {
    }

    public static TheaterLight getInstance() {
        return INSTANCE;
    }

    public void turnOn() {
        System.out.println("打开灯光");
    }

    public void turnOff() {
        System.out.println("关闭灯光");
    }

    public void dim() {
        System.out.println("调暗灯光");
    }

    public void bright() {
        System.out.println("调亮灯光");
    }
}

外观类(TheaterFacade):

public class TheaterFacade {
    private MediaPlayer mediaPlayer;
    private Popcorn popcorn;
    private Projector projector;
    private Screen screen;
    private Stereo stereo;
    private TheaterLight theaterLight;

    public TheaterFacade() {
        mediaPlayer = MediaPlayer.getInstance();
        popcorn = Popcorn.getInstance();
        projector = Projector.getInstance();
        screen = Screen.getInstance();
        stereo = Stereo.getInstance();
        theaterLight = TheaterLight.getInstance();
    }

    public void ready() {
        popcorn.turnOn();
        popcorn.pop();
        screen.down();
        projector.turnOn();
        stereo.turnOn();
        mediaPlayer.turnOn();
        theaterLight.dim();
    }

    public void play() {
        mediaPlayer.play();
    }

    public void pause() {
        mediaPlayer.pause();
    }

    public void end() {
        theaterLight.bright();
        popcorn.turnOff();
        screen.up();
        projector.turnOff();
        stereo.turnOff();
        mediaPlayer.turnOff();
    }
}

客户端调用(Client):

public class Client {
    public static void main(String[] args) {
        TheaterFacade theaterFacade = new TheaterFacade();
        // 准备工作
        theaterFacade.ready();
        System.out.println("-----------------------");
        // 开始播放
        theaterFacade.play();
        System.out.println("-----------------------");
        // 暂停播放
        theaterFacade.pause();
        System.out.println("-----------------------");
        // 播放结束
        theaterFacade.end();
        System.out.println("-----------------------");
    }
}

输出结果:

打开爆米花机
正在出爆米花
下降屏幕
打开投影仪
打开立体声
打开电影播放器
调暗灯光
-----------------------
正在播放电影
-----------------------
暂停播放电影
-----------------------
调亮灯光
关闭爆米花机
升起屏幕
关闭投影仪
关闭立体声
关闭电影播放器
-----------------------

可以看到,Client端的代码非常简单,它只与外观类TheaterFacade产生关系,对应的类图如下:


影院管理问题的类图

4 外观模式的特定的注意事项

1)外观模式对外屏蔽了子系统的细节,因此降低了客户端对子系统使用的复杂性。
2)外观模式将客户端与子系统的关系解耦,让子系统内部的模块更易于维护与扩展。
3)通过合理的使用外观模式,可以帮助我们更好地划分访问层次。
4)当系统需要进行分层设计时,可以考虑外观模式。
5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个外观类,来提供遗留系统的比较清晰简单的接口,让新系统与外观类交互,提高复用性。
6)不能过多或者不合理的使用外观模式,使用外观模式还是直接调用模块,应根据实际问题具体分析。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容