设计模式——1_4 外观(Facade)

文章目录

  • 定义
  • 图纸
  • 一个例子:自动生成一杯茶
    • 沏茶的流程
    • 组合
      • 方式一:直接组合
      • 方法二:外观
  • 碎碎念
    • 多个外观对象
    • 外观和封装
    • 外观和单例
    • 姑妄言之

定义

为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

你可以把 外观模式 理解成控制面板,你可能拥有一部庞大的机器,但是为了使用他,你并不需要理解里面每一个螺丝的作用,只需要按照说明书去控制操作面板就可以调度他


图纸

在这里插入图片描述


一个例子:自动生成一杯茶

很多人喜欢喝茶,但是又嫌沏茶太麻烦了。如果我们现在有一种机器,支持按一个按钮,就生成一杯茶到你手里。那他应该是啥样的呢?这次的例子因为要涉及到多个模块之间的协同,可能会显得比较复杂,我会尽我所能的简化他

准备好了吗?这次的例子开始了:


沏茶的流程

在没有任何自动化的情况下,我们沏茶是这样的:

在这里插入图片描述

如果把他们转换成系统里的一部分的话,他们并不属于相同的模块,比如说:

煮水,肯定是属于 热水壶 的方法。可是一个 热水壶 ,怎么可能会有 冲出茶水 这样的方法定义呢?


所以很显然,上面这个流程里的步骤,需要多个模块的配合,就像这样:
在这里插入图片描述

beans

//水
public class Water implements Comparable<Water> {

    /**
     * 数量
     */
    private float quantity;

    /**
     * 温度
     */
    private float temperature;

    public Water(float quantity, float temperature) {
        this.quantity = quantity;
        this.temperature = temperature;
    }

    public Water(float quantity) {
        this(quantity, 24);//默认水温24度
    }

    //创建一个空的Water对象
    public static Water createEmptyWater() {
        return new Water(0, 0);
    }

    /**
     * 加水
     */
    public void add(Water water) {
        quantity += water.quantity;
        if (temperature == 0) {
            this.temperature = water.temperature;
        }
        water.clear();
    }

    /**
     * 切割一些热水出去
     */
    public Water cut(float quantity) {
        //检查是否足够,如果足够则返还quantity为参数的冷水给client,如果不足则全部返还
        Water result;

        if (quantity < this.quantity) {
            //足够
            result = new Water(quantity, temperature);
            this.quantity -= quantity;
        } else {
            //不够或者刚好
            result = new Water(this.quantity, temperature);
            clear();
        }

        return result;
    }

    //清空当前水对象的信息
    private void clear() {
        this.quantity = 0;
        this.temperature = 0;
    }

    public float getQuantity() {
        return quantity;
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    /**
     * 是热水吗
     * 超过75度视为热水
     */
    public boolean isHot() {
        return temperature >= 75;
    }

    @Override
    public int compareTo(Water o) {
        return Float.compare(this.quantity, o.quantity);
    }
}

//茶叶
public class TeaLeaf {

    /**
     * 茶叶类型
     */
    private final String type;

    public TeaLeaf(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

//茶
public class Tea {

    private float quantity;//数量
    private final String describe;

    public Tea(String describe,float quantity) {
        this.describe = describe;
        this.quantity = quantity;
    }

    public String getDescribe() {
        return describe;
    }
}

水桶、热水壶和盖碗

//水桶
public class Bucket {

    //容量
    private final float capacity;
    //水桶里的水
    private final Water quantity;

    //生成一个空桶
    public Bucket(float capacity) {
        this.capacity = capacity;
        quantity = Water.createEmptyWater();
    }

    public Bucket(float capacity, float water) {
        this(capacity);

        addWater(new Water(water));
    }

    //往水桶里加水
    public boolean addWater(Water water) {
        //检查是否可以容纳,如果足够容纳,则把水全部加入,如果无法容纳则操作失败
        if (capacity - quantity.getQuantity() >= water.getQuantity()) {
            //可以容纳
            quantity.add(water);
            return true;
        }

        return false;//无法容纳
    }

    //给予别人水
    public Water getWater(float quantity) {
        return this.quantity.cut(quantity);
    }
}

//热水壶
public class Kettle {

    //容量
    private final float capacity;
    //当前水量
    private final Water quantity;

    public Kettle(float capacity) {
        this.capacity = capacity;
        this.quantity = Water.createEmptyWater();
    }

    //往热水壶里加水
    public boolean addWater(Water water) {
        //检查是否可以容纳,如果足够容纳,则把水全部加入,如果无法容纳则操作失败
        if (capacity - quantity.getQuantity() >= water.getQuantity()) {
            //可以容纳
            quantity.add(water);
            return true;
        }

        return false;//无法容纳
    }

    //加热水
    public void heatUpWater() {
        quantity.setTemperature(100);//加热到100度
    }

    //倒出水
    public Water getWater(float quantity) {
        return this.quantity.cut(quantity);
    }
}

//盖碗
public class Tureen {

    //容量
    private final float capacity;
    //当前水量
    private final Water quantity;
    //茶叶
    private TeaLeaf teaLeaf;

    public Tureen(float capacity) {
        this.capacity = capacity;
        this.quantity = Water.createEmptyWater();
    }

    //往盖碗里加水
    public boolean addWater(Water water) {
        //检查是否可以容纳,如果足够容纳,则把水全部加入,如果无法容纳则操作失败
        if (capacity - quantity.getQuantity() >= water.getQuantity()) {
            //可以容纳
            quantity.add(water);
            return true;
        }

        return false;//无法容纳
    }

    //生成茶
    public Tea generateTea(float teaQuantity) {
        if (!quantity.isHot()) {
            throw new RuntimeException("必须用热水煮茶");
        } else if (quantity.getQuantity() < teaQuantity) {
            throw new RuntimeException("盖碗里的水数量不足");
        } else {
            return new Tea("这是一份 " + teaLeaf.getType() + " 茶", teaQuantity);
        }
    }

    public TeaLeaf getTeaLeaf() {
        return teaLeaf;
    }

    public void setTeaLeaf(TeaLeaf teaLeaf) {
        this.teaLeaf = teaLeaf;
    }
}

现在我们把所需要的类都创建出来了, client(调用上下文) 可以 创建一个水桶对象A->从A对象里拿到水对象B->把B对象注入热水壶对象C…… ,就像上文说的那种没有任何自动化的方式去生成一杯茶

这对我们的程序来说肯定是不合理的,什么都交给 client 去做,那没有人知道到底会做出什么样的一杯茶,也许编写 client 的人突发奇想,跳过热水壶直接把冷水加入盖碗;又或者把做好的茶倒回水桶……这种情况下,你失去了对整个流程的控制,程序会因为千奇百怪的 client 出现各种各样的异常,除非所有人都遵守规则

理想状态下,我们希望代码可以跟全自动煮茶器一样,我只需要点击一个按钮(调用一个方法),就可以让整个流程动起来,让模块和模块之间像齿轮一样咬合,从而保证 client 可以得到一杯正常的茶


那要怎么做呢?


组合

首先明确一点,Bucket(水桶)Kettle(热水壶)Tureen(盖碗) 一定是分属三个模块中的。我们不可能用继承之类的方式把这些方法都封装到一个类簇中,那么我们就必须把他们组合起来,然后再公开某个接口(比如 A方法),让client

那么问题就来了,A方法 应该被放在哪个类呢?


方式一:直接组合

由于 Tea(茶)是从Tureen中被产出的,很容易就想到直接在 Tureen 中添加 Bucket 对象和Kettle对象。client 在调用的时候再通过 TureenFactory(盖碗工厂) 来直接创建一个可用的 Tureen 对象,就像这样:
在这里插入图片描述

Tureen

//盖碗
public class Tureen {

    //容量
    private final float capacity;
    //当前水量
    private final Water quantity;
    //水桶
    private Bucket bucket;
    //热水壶
    private Kettle kettle;
    //茶叶
    private TeaLeaf teaLeaf;

    public Tureen(float capacity) {
        this.capacity = capacity;
        this.quantity = Water.createEmptyWater();
    }

    public void setBucket(Bucket bucket) {
        this.bucket = bucket;
    }

    public void setKettle(Kettle kettle) {
        this.kettle = kettle;
    }

    //往盖碗里加水
    public boolean addWater(Water water) {
        //检查是否可以容纳,如果足够容纳,则把水全部加入,如果无法容纳则操作失败
        if (capacity - quantity.getQuantity() >= water.getQuantity()) {
            //可以容纳
            quantity.add(water);
            return true;
        }

        return false;//无法容纳
    }

    //生成茶
    public Tea generateTea(float teaQuantity) {
        Water water = bucket.getWater(teaQuantity);//拿到水
        kettle.addWater(water);//加入到热水壶
        kettle.heatUpWater();//加热
        this.addWater(kettle.getWater(teaQuantity));//倒到盖碗中
        return new Tea("这是一份 " + teaLeaf.getType() + " 茶", teaQuantity);
    }

    public TeaLeaf getTeaLeaf() {
        return teaLeaf;
    }

    public void setTeaLeaf(TeaLeaf teaLeaf) {
        this.teaLeaf = teaLeaf;
    }
}

//盖碗工厂
public class TureenFactory {

    public Tureen create(float capacity){
        Tureen tureen = new Tureen(capacity);
        tureen.setBucket(new Bucket(capacity));
        tureen.setKettle(new Kettle(capacity));
        return tureen;
    }
}

在这种方式里 TureenFactory 的存在,可以保证 client 拿到的 Tureen 对象一定可以正常工作。

这个方案现在看起来很美好,我们可以直接通过调用Tureen中的generateTea来保证我们拿到的是一杯可用的茶。但是这是建立在所有提供水的组件对象 都是水桶 和 所有进行加热的组件 都是热水壶的前提下的

你并不能保证这一点,也许将来有人喜欢喝井水,有人喜欢无根之水,还有人喜欢用碳炉煮水而不是热水壶。难道这时候我要让井水、雨水和水桶公用一个父类来方便和盖碗对象组合吗?

这显然是不可能的,所以我们要想想其他组合他们的方法


方法二:外观

让我们回推到最初,其实我们最大的问题,不是如何生成一个可以制作茶水的工具,而是我们需要规范制作一杯茶水的流程。所以我们才不希望 client 直接调用各个模块中的内容

那有没有可能,在底层模块和 client 中间,增加一个 中介层,不要让 client 亲自动手制作茶水,他只需要向 中介对象 发出需要一杯茶的指令,然后就能拿到一杯茶

答案当然是肯定的,就像这样:

在这里插入图片描述

public class TeaMaker {

    public Tea getTea(float quantity, TeaLeaf teaLeaf) {
        //创建水桶对象进行供水
        Bucket bucket = new Bucket(quantity, quantity);
        //创建热水壶对象进行加热
        Kettle kettle = new Kettle(quantity);
        kettle.addWater(bucket.getWater(quantity));
        kettle.heatUpWater();
        //创建盖碗对象用于生成茶
        Tureen tureen = new Tureen(quantity);
        tureen.addWater(kettle.getWater(quantity));
        tureen.setTeaLeaf(teaLeaf);
        return tureen.generateTea(quantity);
    }
}

我们通过 TeaMaker 的对象,实现了 client 和底层对象模块之间的分离

  • 如果我们要新增泡茶的流程那么直接修改 TeaMaker 里的内容就可以了(改变的地方被集中到了一处)
  • 如果是有新的底层模块实现加入到程序中,那么我们也可以通过把 TeaMaker 做成一个类簇的方式,来实现不同对底层模块的调用方式

而这正是一个标准的外观模式实现

外观模式并不是简化了多少你的工作,而是把 很可能出现改变的操作都集中到了一处,让你统一修改,统一调用



碎碎念

多个外观对象

对于一个子系统来说,外观对象是可以存在多个的,你可以针对子系统的不同部分创建不同的外观对象


外观和封装

先声明一点,外观模式并不是对底层模块的封装!

你在使用外观模式的同时,依然可以由 client 直接调用底层模块,外观对象只是给你提供了一个简化的调用方式而已,你完全可以无视他,但是要承担这样做的风险


外观和单例

外观对象通常只是一个用来调用子模块的遥控器,所以都是无状态的,因此很多时候都可以是单例的


姑妄言之

外观对象里的内容通常是对一个庞大的子系统的一部分的抽象

这就跟我们每天看到的热搜新闻一样。为什么现在的新闻三天两头就反转,因为很多媒体已经失去了对新闻的严谨性,遇到一件新事,他们看重的是速度,而不能为大众提供事件的全貌,这是不负责任的表现。

一件事只看到一部分和全貌的差别是很大的,这就像盲人摸象,摸到什么就觉得大象是啥样的。上例中的 TeaMaker 是用来沏茶的,但是也许完整的子系统其实是用来煲汤的也说不定,你只是看到了 TeaMaker 而已

所以在这个浮躁的社会里,面对所有新闻都请先别站队,保持独立思考,尽可能让子弹飞一会




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/321456.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详细探讨mfc140.dll丢失的解决方法,并比较各种方法的优劣

mfc140.dll是Microsoft Foundation Class (MFC) 库中一个重要的DLL文件&#xff0c;它包含了多个执行程序使用的函数和资源。这个库通常用于开发Windows操作系统上的应用程序。但有时会发生mfc140.dll缺失或损坏的错误&#xff0c;导致一些依赖它的应用程序无法运行。今天的这篇…

微信强制分享红包裂变系统源码

源码介绍 微信裂变引流系统源码&#xff1a;高效吸粉&#xff0c;轻松变现 探索这款创新的微信裂变引流系统源码&#xff0c;它将为你的广告推广活动注入强大的动力。凭借其丰富的功能和卓越的性能&#xff0c;它将成为你实现高效引流、粉丝增长和变现转化的强大工具。 该系…

精确掌控并发:固定时间窗口算法在分布式环境下并发流量控制的设计与实现

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;14&#xff09;篇。点击上方关注&#xff0c;深入了解支付系统的方方面面。 本篇主要介绍分布式场景下常用的并发流量控制方案&#xff0c;包括固定时间窗口、滑动时间窗口、漏桶、令牌桶、分布式消息中间件…

通过开源端点可见性改善网络安全响应

在当今复杂的数字环境中&#xff0c;企业内的许多不同端点&#xff08;从数据中心的服务器到咖啡店的笔记本电脑&#xff09;创建了巨大且多样化的攻击面。每个设备都存在网络安全威胁的机会&#xff0c;每个设备都有其独特的特征和复杂性。攻击者使用的多种攻击媒介不仅是一个…

详解SpringCloud微服务技术栈:强推!源码跟踪分析Ribbon负载均衡原理、Eureka服务部署

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;详解SpringCloud微服务技术栈&#xff1a;认识微服务、服务拆分与远程调用 &#x1f4da;订阅专栏&#xff1a;微服务技术全家桶…

01 SpringMVC的快速理解

1.1 如图所示&#xff0c;SpringMVC负责表述层&#xff08;控制层Controller&#xff09;实现简化&#xff01; SpringMVC的作用主要覆盖的是表述层&#xff0c;例如&#xff1a; 请求映射、数据输入、视图界面、请求分发、表单回显、会话控制、过滤拦截、异步交互、文件上传…

adb 常用命令汇总

目录 adb 常用命令 1、显示已连接的设备列表 2、进入设备 3、安装 APK 文件到设备 4、卸载指定包名的应用 5、从设备中复制文件到本地 6、将本地文件复制到设备 7、查看设备日志信息 8、重启设备 9、截取设备屏幕截图 10、屏幕分辨率 11、屏幕密度 12、显示设备的…

PyTorch损失函数(二)

损失函数 5、nn.L1Loss nn.L1Loss是一个用于计算输入和目标之间差异的损失函数&#xff0c;它计算输入和目标之间的绝对值差异。 主要参数&#xff1a; reduction&#xff1a;计算模式&#xff0c;可以是none、sum或mean。 none&#xff1a;逐个元素计算损失&#xff0c;返…

书生·浦语大模型实战营笔记(四)

Finetune模型微调 直接使用现成的大语言模型&#xff0c;在某些场景下效果不好&#xff0c;需要根据具体场景进行微调 增量预训练&#xff1a;投喂垂类领域知识 陈述形式&#xff0c;无问答&#xff0c;即只有assistant 指令跟随&#xff1a;system-user-assistant XTuner …

树莓派4B-Python-使用PCA9685控制舵机云台+跟随人脸转动

系列文章 树莓派4B-Python-控制舵机树莓派-Pico控制舵机树莓派4B-Python-使用PCA9685控制舵机云台跟随人脸转动&#xff08;本文章&#xff09; 目录 系列文章前言一、SG90s舵机是什么&#xff1f;二、PCA9685与舵机信号线的接线图三、控制SG90s云台&#xff08;也可用来测试舵…

YOLOv5改进 | 主干篇 | 12月最新成果UniRepLknet特征提取网络(附对比试验效果图)

一、本文介绍 本文给大家带来的改进机制是特征提取网络UniRepLknet,其也是发表于今年12月份的最新特征提取网络,该网络结构的重点在于使用Dilated Reparam Block和大核心指导原则,强调了高效的结构进行通道间通讯和空间聚合,以及使用带扩张的小核心进行重新参数化,该网络…

C++输入输出和文件

文章目录 一. 流, 缓冲区和iostream文件二. 使用cout进行输出1. 用cout进行格式化2. 刷新输出缓冲区 三. 使用cin进行输入1. cin>>如何检查输入2. 流状态3. 其他istream类方法 四. 文件输入和输出1. 简单的文件I/O2. 文件模式3. 随机存取4. 内核格式化 To be continue...…

使用docker搭建LNMP架构

目录 环境准备 下载安装包 服务器环境 任务分析 nginx部分 建立工作目录 编写 Dockerfile 脚本 准备 nginx.conf 配置文件 生成镜像 创建自定义网络 启动镜像容器 验证nginx MySQL部分 建立工作目录 编写 Dockerfile 准备 my.cnf 配置文件 生成镜像 启动镜像…

C语言基础内容(七)——第07章_结构体与共同体

文章目录 第07章_结构体与共用体本章专题脉络1、结构体(struct)类型的基本使用1.1 为什么需要结构体?1.2 结构体的理解1.3 声明结构体1.4 声明结构体变量并调用成员1.5 举例1.6 小 结2、进一步认识结构体2.1 结构体嵌套2.2 结构体占用空间2.3 结构体变量的赋值操作3、结构体数…

JDK8-JDK17版本升级

局部变量类型推断 switch表达式 文本块 Records 记录Records是添加到 Java 14 的一项新功能。它允许你创建用于存储数据的类。它类似于 POJO 类&#xff0c;但代码少得多&#xff1b;大多数开发人员使用 Lombok 生成 POJO 类&#xff0c;但是有了记录&#xff0c;你就不需要使…

保卫战小游戏

欢迎来到程序小院 保卫战 玩法&#xff1a;当鬼子进入射击范围内点击鼠标左键射击&#xff0c;不要让鬼子越过炮台哦&#xff0c;快去杀鬼子去吧^^。开始游戏https://www.ormcc.com/play/gameStart/249 html <div style"position: relative;" id"gameDiv&q…

K 个一组翻转链表(链表反转,固定长度反转)(困难)

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你链表的头节点head&#xff0c;每k个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是k的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。…

Spring Boot - Application Events 的发布顺序_ApplicationContextInitializedEvent

文章目录 Pre概述Code源码分析 Pre Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent 概述 Spring Boot 的广播机制是基于观察者模式实现的&#xff0c…

Spring Boot - 利用Resilience4j-RateLimiter进行流量控制和服务降级

文章目录 Resilience4j概述Resilience4j官方地址Resilience4j-RateLimiter微服务演示Payment processorPOM配置文件ServiceController Payment servicePOMModelServiceRestConfigController配置验证 探究 Rate Limiting请求三次 &#xff0c;观察等待15秒连续访问6次 Resilienc…

安装nodejs出现问题

Error: EPERM: operation not permitted, mkdir… 全局安装express模块进行测试时&#xff1a; npm install express -g出现&#xff1a; 表示nodejs的安装目录无权限&#xff0c;根据错误日志的信息&#xff0c;定位到安装目录下&#xff1a; 点击属性&#xff1a; 点击编…