文章目录
- 定义
- 图纸
- 一个例子:自动生成一杯茶
- 沏茶的流程
- 组合
- 方式一:直接组合
- 方法二:外观
- 碎碎念
- 多个外观对象
- 外观和封装
- 外观和单例
- 姑妄言之
定义
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
你可以把 外观模式 理解成控制面板,你可能拥有一部庞大的机器,但是为了使用他,你并不需要理解里面每一个螺丝的作用,只需要按照说明书去控制操作面板就可以调度他
图纸
一个例子:自动生成一杯茶
很多人喜欢喝茶,但是又嫌沏茶太麻烦了。如果我们现在有一种机器,支持按一个按钮,就生成一杯茶到你手里。那他应该是啥样的呢?这次的例子因为要涉及到多个模块之间的协同,可能会显得比较复杂,我会尽我所能的简化他
准备好了吗?这次的例子开始了:
沏茶的流程
在没有任何自动化的情况下,我们沏茶是这样的:
如果把他们转换成系统里的一部分的话,他们并不属于相同的模块,比如说:
煮水,肯定是属于 热水壶 的方法。可是一个 热水壶 ,怎么可能会有 冲出茶水 这样的方法定义呢?
所以很显然,上面这个流程里的步骤,需要多个模块的配合,就像这样:
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 而已
所以在这个浮躁的社会里,面对所有新闻都请先别站队,保持独立思考,尽可能让子弹飞一会
万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容