• 设计模式——外观模式


    一、外观模式

    1.1 概述

    在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。

    有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。

    软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。

    1.2 定义

    外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

    在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

    1.3 结构

    • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
    • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
    • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

    1.4 案例

    小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:
    在这里插入图片描述

    //灯类
    public class Light {
        public void on() {
            System.out.println("打开了灯....");
        }
    
        public void off() {
            System.out.println("关闭了灯....");
        }
    }
    
    //电视类
    public class TV {
        public void on() {
            System.out.println("打开了电视....");
        }
    
        public void off() {
            System.out.println("关闭了电视....");
        }
    }
    
    //控制类
    public class AirCondition {
        public void on() {
            System.out.println("打开了空调....");
        }
    
        public void off() {
            System.out.println("关闭了空调....");
        }
    }
    
    //智能音箱
    public class SmartAppliancesFacade {
    
        private Light light;
        private TV tv;
        private AirCondition airCondition;
    
        public SmartAppliancesFacade() {
            light = new Light();
            tv = new TV();
            airCondition = new AirCondition();
        }
    
        public void say(String message) {
            if(message.contains("打开")) {
                on();
            } else if(message.contains("关闭")) {
                off();
            } else {
                System.out.println("我还听不懂你说的!!!");
            }
        }
    
        //起床后一键开电器
        private void on() {
            System.out.println("起床了");
            light.on();
            tv.on();
            airCondition.on();
        }
    
        //睡觉一键关电器
        private void off() {
            System.out.println("睡觉了");
            light.off();
            tv.off();
            airCondition.off();
        }
    }
    
    //测试类
    public class Client {
        public static void main(String[] args) {
            //创建外观对象
            SmartAppliancesFacade facade = new SmartAppliancesFacade();
            //客户端直接与外观对象进行交互
            facade.say("打开家电");
            facade.say("关闭家电");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    1.5 优缺点

    优点

    • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
    • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
    • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

    缺点

    • 不能很好地限制客户使用子系统类,很容易带来未知风险。
    • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

    1.6 使用场景

    • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
    • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
    • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

    1.7 源码解析

    使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。
    在这里插入图片描述
    RequestFacade类就使用了外观模式。先看结构图:
    在这里插入图片描述
    定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。
    这里我看了好久都觉得是静态代理模式或者装饰器模式,直到看了httpservletrequest接口的源码:

    在这里插入图片描述可以看到httpservletrequest只有两个接口,也就是其实RequestFacade 也用到了静态代理模式,但是只是对部分方法进行了静态代理。RequestFacade中其他方法是采用了外观模式,只是用了相同的方法名来包装了request中的方法。
    这里也体现了外观模式的一个用法,当外观类中只有一个子系统时,他其实很像代理模式。虽然外观类和子系统实现了相同的接口,但是外观类需要包装接口中没有的方法。

  • 相关阅读:
    Vue3分页组件基础使用 以及 给表格增加自增序列
    金仓数据库KingbaseES客户端编程接口指南-JDBC(5. JDBC 查询结果集处理)
    2022CTF培训(二)Hook进阶&反调试
    Redis Lua沙盒绕过命令执行(CVE-2022-0543)
    使用ADExplorer导出域内信息
    Postgresql中触发器的使用
    【Redis】数据结构---String
    Java项目:JSP家教服务找家教平台网站项目
    浅谈—IBIS入门理解(二)
    建议别瞎卷,GitHub乱杀,MySQL DBA 攻坚指南一出,阿里数据库专家都解脱了
  • 原文地址:https://blog.csdn.net/qq_51114283/article/details/125884787