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

news2025/4/24 13:25:39

文章目录

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

定义

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

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


图纸

在这里插入图片描述


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

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

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


沏茶的流程

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

在这里插入图片描述

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

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


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

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 而已

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




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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows系统Outlook邮件备份导出与导入教程

注意&#xff1a;微软商店UWP版本outlook客户端暂时不支持邮件备份&#xff01;而Microsoft Office2003-目前(2021)中的outlook客户端才支持邮件备份。所以&#xff0c;想要备碧桂园集团邮箱邮件&#xff0c;请安装或者登录Microsoft Office中的outlook客户端以进行邮件的备份。…

Vue3+Vite连接高德地图JS API——地图显示、输入搜索

1 开通高德地图Web端JS API服务 1、进入高德地图API官网&#xff08;https://lbs.amap.com/&#xff09;&#xff1a; 2、注册登录。 3、进入控制台。 4、点击“应用管理”&#xff0c;点击“我的应用”&#xff0c;创建新应用。 5、添加Key&#xff0c;服务平台选择“Web端&…

Mysql数据库高版本向低版本迁移方法

操作步骤 1、首先低版本Mysql创建数据库 2、使用navicat工具&#xff0c;复制高版本数据库的表 3、在低版本数据库中粘贴&#xff0c;弹出数据传输界面&#xff0c;选项去掉包含字符集、包含引擎及表类型 使用该版本实现了Mysql8.0向Mysql5.5的迁移&#xff0c;如果在Mysql8.0生…

VS游戏打包教程

我用得天天酷跑小游戏做的例子 1:安装打包插件 2:在解决方案里新建一个项目 3:新建一个setup项目 4:界面如下(通过右键folder,可以创建folder目录和输出) 5:素材文件 6:素材放完了就项目输出 7:创建快捷方式 右键这个主输出选择第一个create shortcut 8:将这个快捷方式,拖到,…

《最新出炉》系列入门篇-Python+Playwright自动化测试-9-页面(page)

1.简介 通过前边的讲解和学习&#xff0c;细心认真地小伙伴或者童鞋们可能发现在Playwright中&#xff0c;没有Element这个概念&#xff0c;只有Page的概念&#xff0c;Page不仅仅指的是某个页面&#xff0c;例如页面间的跳转等&#xff0c;还包含了所有元素、事件的概念&#…

ESU毅速丨制造企业需不需要建设增材制造中心?

随着科技的不断发展&#xff0c;增材制造技术已经成为制造行业的新宠。越来越多的企业开始考虑建设增材制造中心&#xff0c;以提高生产效率、降低成本、加速产品创新。但是&#xff0c;对于制造企业来说&#xff0c;是否需要建设增材制造中心呢&#xff1f; 首先&#xff0c;我…

EfficientSAM 代码推理

SA网站主页&#xff1a;Segment Anything | Meta AI 论文主页&#xff1a;EfficientSAM 代码地址&#xff1a;https://github.com/yformer/EfficientSAM 官方给的推理代码是CPU版本的&#xff0c;如果想使用GPU推理需要自己修改一下 经过推理测试3090GPU&#xff0c;官方测试…

从字节码角度分析i++与++i的区别

情况一 : 当i与i没有对象接收值时, 没有任何区别 情况二 : 当i与i没有对象接收值时 可以看到 i时, 先把i值10从局部变量表拿到操作数栈(29), 之后执行iinc, 直接修改局部变量表里面的值10修改成11(30), 最后将操作树栈里面的值赋值给i4(33) (由于iinc直接修改的局部变量表里面…

鸿蒙开发-UI-布局

鸿蒙开发-序言 鸿蒙开发-工具 鸿蒙开发-初体验 鸿蒙开发-运行机制 鸿蒙开发-运行机制-Stage模型 鸿蒙开发-UI 鸿蒙开发-UI-组件 鸿蒙开发-UI-组件-状态管理 鸿蒙开发-UI-应用-状态管理 鸿蒙开发-UI-渲染控制 文章目录 前言 一、布局概述 1.布局结构 2.布局元素组成 3.布局分类 …

微信小程序快速入门03

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、生命周期生…

花了三天的时间做了一个多功能 AI 助手

嗨&#xff01;我是团子&#xff0c;大家新年快乐呀~ 前几天看到一些好朋友在朋友圈晒自己的年度总结&#xff0c;立新年 Flag&#xff0c;看到大家一年满满的收获&#xff0c;再看看自己&#xff0c;不由得想再看看人家&#xff0c;然后再看看自己&#xff0c;然后再看看人家…

(南京观海微电子)——色温介绍

色温是表示光线中包含颜色成分的一个计量单位。从理论上说&#xff0c;黑体温度指绝对黑体从绝对零度&#xff08;&#xff0d;273℃&#xff09;开始加温后所呈现的颜色。黑体在受热后&#xff0c;逐渐由黑变红&#xff0c;转黄&#xff0c;发白&#xff0c;最后发出蓝色光。当…

NFTScan | 01.01~01.07 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.01.01~ 2024.01.07 NFT Hot News 01/ 空投 | Mint Blockchain 将于 2024 年 1 月 10 号启动 Mint Genesis NFT 空投活动 1 月 1 日&#xff0c;Mint Blockchain 宣布将于 2024 年 1…

双指针算法: 有效三角形的个数

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 前言 声明…

即将推出的 OpenWrt One/AP-24.XY:OpenWrt 和 Banana Pi 合作路由器板

OpenWrt开发人员正在与Banana Pi合作开发OpenWrt One/AP-24.XY路由器板。OpenWrt 是一个轻量级嵌入式 Linux 操作系统&#xff0c;支持近 1,800 个路由器和其他设备。然而&#xff0c;这将是第一块由 OpenWrt 直接开发的路由器板。 该主板将基于 MediaTek MT7981B (Filogic 82…

【教学类-43-19】20240113 数独(一) 3-5-6-7-8-10宫格 无空行A4模板

作品展示&#xff1a; 3宫格 5宫格 6宫格 7宫格 8宫格 10宫格&#xff0c;题目连在一起 背景需求&#xff1a; 制作十宫格数独模板&#xff0c;为了凑满20行&#xff0c;删除了每个数独题之间的行列分割线 【教学类-43-18】A4最终版 20240111 数独11.0 十宫格X*YZ套(n10)&am…

构建中国人自己的私人GPT-有道GPT

创作不易&#xff0c;请大家多鼓励支持。 在现实生活中&#xff0c;很多人的资料是不愿意公布在互联网上的&#xff0c;但是我们又要使用人工智能的能力帮我们处理文件、做决策、执行命令那怎么办呢&#xff1f;于是我们构建自己或公司的私人GPT变得非常重要。 先看效果 一、…

[Windows] Win10 常用快捷键

文章目录 &#x1f680; [Windows] Win10 常用快捷键&#x1f310; Windows 操作系统&#x1f525; Windows 10 &#x1f310; Windows 10 快捷键概览&#x1f525; 基本快捷键&#x1f525; 窗口快捷键&#x1f525; 功能快捷键 &#x1f4dd; 小结 &#x1f680; [Windows] W…

网站后台拿Webshell

通过注入或者其他途径&#xff0c;获取网站管理员账号和密码后&#xff0c;找到后台登录地址&#xff0c;登录后&#xff0c;寻找后台漏洞上传网页后门&#xff0c;获取网站的webshell webshell的作用是方便攻击者&#xff0c;webshel是拥有fso权限&#xff0c;根据fso权限的不…

Android Studio 虚拟机 Unknown Error 解决

前言 尝试了网上很多解决方式&#xff0c;但很遗憾&#xff0c;都没效果&#xff1b; 于是我就想啊&#x1f914;&#xff0c;虚拟机属于SDK的一部分&#xff0c;那有没有一种可能&#xff0c;是SDK出了问题&#xff1b; 于是我就换了新的SDK&#xff0c;结果 ---- 完美解决…