设计模式——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;}@Overridepublic 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.mzph.cn/news/620623.shtml

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

相关文章

【120版本】最新谷歌浏览器驱动下载地址

在使用selenium时可能会遇到谷歌浏览器和谷歌驱动器版本不一致的问题&#xff0c;并且国内可以搜到的谷歌浏览器下载地址里面最新的驱动器只有114版本的&#xff0c;但目前谷歌浏览器最新版本是120。所以这里记录下最新版本120谷歌驱动器下载地址&#xff1a; Chrome for Test…

Dobbo---分布式系统通信方式

通信方式 分布式系统通信方式1. RMIRMI 通信实现案例2. RPC常用RPC框架 分布式系统通信方式 1. RMI RMI ( Remote Method Invocation 远程方法调用) 图1.1 客户端-服务端通信方式 客户端将要调用的方法及参数&#xff0c;打包为辅助对象&#xff0c;通过网络socket&#xff…

【自控实验】3. 带有饱和非线性环节控制系统相平面分析

本科课程实验报告&#xff0c;有太多公式和图片了&#xff0c;干脆直接转成图片了 仅分享和记录&#xff0c;不保证全对 实验内容&#xff1a; 有无非线性环节的相轨迹对比&#xff0c;并求超调量。 在输入单位阶跃信号Xsr时&#xff0c;用示波器观察和记录系统输入饱和非线…

Hadoop-HA高可用

一、集群规划 二、HDFS高可用 官方地址 在opt目录下创建一个ha文件夹&#xff0c;将/opt/module/下的 hadoop-3.1.3拷贝到/opt/ha目录下&#xff08;记得删除data 和 log目录&#xff09; 配置core-site.xml hdfs-site.xml <configuration><!-- NameNode数据存…

个人网站制作 Part 4 添加响应式设计 | Web开发项目

文章目录 &#x1f469;‍&#x1f4bb; 基础Web开发练手项目系列&#xff1a;个人网站制作&#x1f680; 添加响应式设计&#x1f528;移动优先的响应式样式&#x1f527;步骤 1: 添加媒体查询 &#x1f528;图片和布局调整&#x1f527;步骤 2: 使用响应式图片&#x1f527;步…

如何使用网络测试仪构造特殊流量

为什么要仿真特殊流量 在现网中&#xff0c;网络流量时常伴随着突发&#xff0c;突发流量可能会造成网络的拥塞&#xff0c;从而产生丢包、抖动和时延&#xff0c;导致网络服务质量整体下降。面对宏观上的突发&#xff0c;通常采用在网络设备入向限速或者流量整形功能来消除突…

使用代理IP池实现多线程爬虫的方法

目录 前言 代理IP的概念和作用 为什么使用代理IP池 代理IP池的实现步骤 代码实现 注意事项 总结 前言 随着互联网的发展&#xff0c;爬虫技术在各个领域中被广泛应用。然而&#xff0c;目标网站对爬虫的限制也日益严格&#xff0c;例如限制单个IP的请求频率。为了解决这…

从0到1:如何建立一个大规模多语言代码生成预训练模型

国产AI辅助编程工具 CodeGeeX 是一个使用AI大模型为基座的辅助编程工具&#xff0c;帮助开发人员更快的编写代码。可以自动完成整个函数的编写&#xff0c;只需要根据注释或Tab按键即可。它已经在Java、JavaScript和Python等二十多种语言上进行了训练&#xff0c;并基于大量公开…

三轴加速度计LIS2DW12开发(3)----检测活动和静止状态

e2studio开发三轴加速度计LIS2DW12.3--检测活动和静止状态 概述视频教学样品申请源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uart_callback ()…

mongoose6.0版以上操作mongodb数据库的基本使用

1、介绍 Mongoose 是一个对象文档模型库&#xff0c;官网 http://www.mongoosejs.net/ 2、作用 方便使用代码操作 mongodb 数据库 3、使用流程 3.1、链接数据库 //1. 安装 mongoose---> npm install mongoose --save//2. 导入 mongoose const mongoose require(&quo…

航模遥控开关电路图大全

航模遥控开关电路图&#xff08;一&#xff09;&#xff1a;单通道航模遥控器的构造 遥控装置一般应用于车模、航模等领域&#xff0c;用以实现对靶机、航模、玩具等的自动控制。下面介绍一种无线比例电机遥控器的制作方法。它选用易购元件&#xff0c;具有原理简单、性能可靠…

28 星际旋转

效果演示 实现了一个太阳系动画&#xff0c;其中包括了地球、火星、金星、土星、水星、天王星、海王星以及火卫二号等行星的动画效果。太阳系的行星都被放在一个固定的容器中&#xff0c;并使用CSS动画来实现旋转和移动的效果。当太阳系的行星绕着太阳运行时&#xff0c;它们会…

电子学会C/C++编程等级考试2021年09月(四级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:最佳路径 如下所示的由正整数数字构成的三角形: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最佳路径…

Rust-trait

Rust语言中的trait是非常重要的概念。 在Rust中&#xff0c;trait这一个概念承担了多种职责。在中文里&#xff0c;trait可以翻译为“特征”“特点”“特性”等。 成员方法 trait中可以定义函数。用例子来说明&#xff0c;我们定义如下的trait: 上面这个trait包含了一个方法…

【C++入门到精通】智能指针 [ C++入门 ]

阅读导航 引言一、什么是智能指针二、为什么需要智能指针三、内存泄漏1. 什么是内存泄漏&#xff0c;内存泄漏的危害2. 内存泄漏的示例&#xff0c;以及解决方法3. 内存泄漏分类&#xff08;1&#xff09;堆内存泄漏(Heap leak)&#xff08;2&#xff09;系统资源泄漏 4. 如何检…

FFmpeg 的使用与Docker安装流媒体服务器

本文阐述的均为命令行的使用方式&#xff0c;并不牵扯FFmpeg 的 C音视频开发内容&#xff0c;补充一句&#xff0c;C的资料真的少&#xff0c;能把C学好的人&#xff0c;我真的是觉得巨佬。 我主要是使用FFmpeg 推流方面的知识&#xff0c;案例大都是靠近这方面。 一、FFmpeg…

如何在“Microsoft Visual Studio”中使用OpenCV构建应用程序

我在这里描述的所有内容都将应用于 OpenCV 的界面。我首先假设您已经阅读并成功完成了 Windows 中的安装教程。因此&#xff0c;在进一步操作之前&#xff0c;请确保您有一个包含 OpenCV 头文件和二进制文件的 OpenCV 目录&#xff0c;并且您已按照此处所述设置环境变量 设置 O…

迅腾文化用网络集成化生态系统助力品牌之路的坚实后盾

商业竞争激烈&#xff0c;品牌不仅是企业的标志和形象&#xff0c;更是其核心价值和竞争力的体现。然而&#xff0c;企业在品牌推广过程中面临着诸多如缺乏有效的渠道管理、品牌形象模糊以及竞争激烈的市场环境等。这些阻碍着企业的品牌发展和市场占有率的提升。本文将通过企业…

C语言辨析——深入理解格式字符的用法

1. 问题 下面程序为什么的输出结果为什么不是25而是0&#xff1f;问题出在哪&#xff1f; #include <stdio.h> #include <math.h> int main() {int a3,b4; printf("%d\n",pow(a,2)pow(b,2)); return 0; } 2. 分析 函数pow的返回类型是double&…

线上剧本杀小程序搭建,未来线上剧本杀有哪些发展优势?

剧本杀游戏是当下比较流行的一种新型游戏模式&#xff0c;它能够让玩家在游戏中进行角色扮演&#xff0c;体验不同的角色人生&#xff0c;沉浸式玩游戏&#xff0c;因此受到了众多年轻人的喜欢。随着互联网科技的发展&#xff0c;剧本杀的发展也转型到了互联网上&#xff0c;为…