汉堡中的设计模式
Posted 码农Amg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了汉堡中的设计模式相关的知识,希望对你有一定的参考价值。
艺术来源于生活,有时候灵感真的就在那么一瞬间
看到上图这个板烧没有,这就是我今晚的晚餐了;走进麦当劳里面,有很多很多的汉堡
板烧鸡腿堡、麦辣鸡腿堡、麦香堡、深海鳕鱼堡....
这些个汉堡的制作方式,似乎有着一种很明显的相似感,汉堡的制作方式非常像之前学习过的一种设计模式,今天我们就来聊一聊
我们不妨更加仔细的思考一下上面的问题
- 我要做一个【板烧鸡腿堡】
- 取出两块面包,烤热
- 放上烤热的腌制好的鸡腿肉,撒上独特的酱汁
- 撒上蔬菜
- 组合成汉堡
- 装在纸袋里面
- 我要做一个【麦辣鸡腿堡】
- 取出两块面包,烤热
- 放上炸制好的鸡腿肉,撒上独特的酱汁
- 撒上蔬菜
- 组合成汉堡
- 装在纸袋里面
- 我要做一个【新奥尔良烤鸡腿堡】
- 取出两块面包,烤热
- 放上新奥尔良鸡腿肉,撒上独特的酱汁
- 撒上蔬菜
- 组合成汉堡
- 装在纸袋里面
- 制作其他汉堡......
有图有真相
可以发现,除了步骤2存在差异之外,其他步骤其实都是一样的,那么我们是否可以将步骤2给抽取出来,其他步骤都不变
就好像模板一般
那么我们是否可以这么做,改造一下模型图,将不变的写在超类里面,变化的由子类决定
这就是模板设计模式的思想,下面是《Head First 设计模式》对模板设计模式的解释
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
我们现在就使用模板设计模式来改造一下我们的模型图
改造后的模型图
从上图就很清晰的看到,模板设计的理念就是定义一个骨架,把公用部分挪到超类中,同时又把会变化的步骤交给子类来决定
世间众多的设计模式,都要做到一件事,那就是 封装变化点
何谓封装变化点?
- 模板设计模式:把【个性化操作(这就是变化点)】操作延迟到未来实现,最后插入到整个定义好步骤的算法中
- 类似的还有策略模式,同样是封装算法家族
- 还有桥接模式,实例被定义成抽象的,通过接口交互,允许实例自由的变化而不会影响到另外的实例
- 还有很多很多......
既然模型图出来了,那么就按照模型图来把代码敲一敲吧,天上飞的理念还是得有落地的那一刻,这是很重要的,实践出真知
/**
* 定义制作汉堡包的模板流程
* @author Amg
* @date 2021/9/24
*/
public abstract class AbstractMakeHamburgerHandler {
//定义层final,确保子类不会把我“骨架”都修改了
public final void execute() {
baking();
customize();
vegetable();
compose();
finish();
hook();
}
private void baking() {
System.out.println("取出两块面包,烤热");
}
/**
* 自定义步骤,可以添加你想添加的食材
*/
protected abstract void customize();
private void compose() {
System.out.println("组合汉堡");
}
private void vegetable() {
System.out.println("撒上蔬菜");
}
private void packing() {
System.out.println("用纸袋包裹着");
}
private void finish() {
System.out.println("完成制作!请慢用");
}
protected void hook() {
//这个hook方法就是一个程序“钩子”,待会会解释
}
}
/**
* 板烧鸡腿堡
* @author Amg
* @date 2021/9/24
*/
public class banshao extends AbstractMakeHamburgerHandler {
@Override
protected void customize() {
System.out.println("[制作板烧鸡腿堡] 专属的腌制好的鸡腿肉,撒上独特的酱汁");
}
}
/**
* 麦辣鸡腿堡
*
* @author Amg
* @date 2021/9/24
*/
public class mailajituibao extends AbstractMakeHamburgerHandler {
@Override
protected void customize() {
System.out.println("[制作麦辣鸡腿堡] 放上炸制好的鸡腿肉,撒上独特的酱汁");
}
}
//新奥尔良就不实现了,有兴趣的朋友可以自行敲一敲(主要是肯德基你跑错地方了,这里是麦当劳专场)
public class Client {
public static void main(String[] args) {
AbstractMakeHamburgerHandler banshao = new banshao();
banshao.execute();
System.out.println("---------------------------------");
mailajituibao mailajituibao = new mailajituibao();
mailajituibao.execute();
}
}
输出结果
现在我们模型图画了,代码也敲了,那么模板设计模式有什么优缺点吗?
优点
- 抽取共用部分到超类中,让程序更加的简洁
- 封装不变的对象,扩展变化的部分(符合开闭原则)
缺点
- 采用继承结构,理论上来说,每扩展一个汉堡(推出新品)就需要创建一个新的类继承超类,如果有100个汉堡,就得有100个类,这就会显示很膨胀(好处是Java 8引入了消费者接口,我们可以使用这个接口,而不必创建一大堆的新类)
简单讲解一下Consumer(消费者)接口
先看看这个接口里面的方法
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
我们得知道消费者这个角色的第一任务就是消费嘛,我不管什么乱七八糟的,我只知道调用accept方法,我就要去消费
至于andThen方法,返回一个组合的Consumer ,它依次执行此操作和after操作。 如果执行任一操作引发异常,则将其转发给组合操作的调用者。 如果执行此操作抛出异常,则不会执行after操作(这段是直接翻译文档过来的,解释得很透彻了)
Java8 Stream中大量使用到消费者和生产者,有兴趣的朋友也可以去了解一下(话说jdk都出到17了...)
所有,如果以Consumer方式使用模板模式,是怎么样的?直接上代码
/**
* jdk1.8之后做汉堡
* @author Amg
* @date 2021/9/24
*/
public class MakeHamburgerHandler {
/**
* 这个方法进行复用
* @param consumer jdk1.8之后出现的【消费者】接口,接收参数,无返回值,调用accept方法就把信息给输出出来
*/
private void execute(Consumer<String> consumer) {
baking();
consumer.accept(null);
vegetable();
compose();
packing();
finish();
}
private void baking() {
System.out.println("取出两块面包,烤热");
}
private void compose() {
System.out.println("组合汉堡");
}
private void vegetable() {
System.out.println("撒上蔬菜");
}
private void packing() {
System.out.println("用纸袋包裹着");
}
private void finish() {
System.out.println("制作完成,客官请慢用!");
}
/**
* 板烧鸡腿堡
*/
public void banshao() {
execute(a -> System.out.println("[制作板烧鸡腿堡] 专属的腌制好的鸡腿肉,撒上独特的酱汁"));
}
/**
* 麦辣鸡腿堡
*/
public void mailajituibao() {
execute(a -> System.out.println("[制作麦辣鸡腿堡] 放上炸制好的鸡腿肉,撒上独特的酱汁"));
}
}
/**
* 使用JDK1.8之后提供的supplier、consumer接口,可以不再不要抽象类,以及减少类对象的创建
* 而且把相同的业务给放在同一个类里面,方便后续的维护;
* <p></p>
* <p>但是思考一下,这样子就会违反OCP原则,上架和下架汉堡、修改汉堡佐料等等操作都需要去修改{@link MakeHamburgerHandler}</p>
*
* @author Amg
* @date 2021/9/24
*/
public class Client {
public static void main(String[] args) {
MakeHamburgerHandler makeHamburgerHandler = new MakeHamburgerHandler();
makeHamburgerHandler.banshao();
System.out.println("------------------jdk1.8之后模板模式的使用------------------------");
makeHamburgerHandler.mailajituibao();
System.out.println("------------------jdk1.8之后模板模式的使用------------------------");
}
}
代码上的注释也很清晰了,这里再总结一下,使用Consumer接口,我们可以不再需要抽象类、以及一大堆的派生子类,想要什么汉堡就包装一下execute方法即可,可以说是非常的好使
但是随之而来的问题也是,我们每次修改都会动到这个类,所以是违反了OCP原则的
最最最最后一个问题,就是上面遗留的hook方法有什么用?
上面遗留的hook方法其实是一个程序钩子,他一般是空方法体或者默认实现,作用是让子类有能力对算法的不同点进行挂钩,意思就是执行或者不执行额外的逻辑
通俗点就是麦当劳在点完餐之后通常还会有一些推荐点餐,我爱吃薯条(就是你了),你可以选择要还是不要,如果我想在板烧套餐里面添加,麦辣鸡腿堡套餐里面不要,要怎么做呢?
修改我们的代码
public final void execute() {
baking();
customize();
vegetable();
compose();
finish();
//将hook方法更名为isReceiveChips
if (isReceiveChips()) {
System.out.println("薯条添加完毕!");
}
}
/**
* 是否接收推荐过来的薯条,默认不要
* @return false
*/
protected boolean isReceiveChips() {
return false;
}
//获取用户输入
protected String getUserInput() {
String answer;
System.out.println("是否还需要来一份薯条呢?[y/n]");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException e) {
answer = "n";
}
return answer;
}
//板烧子类覆盖这个方法
@Override
protected boolean isReceiveChips() {
String answer = getUserInput();
if (answer.equalsIgnoreCase("y"))
return true;
return false;
}
输出的结果
可以看到,我们在板烧套餐里面重写了isReceiveChips方法,并且返回true;而在麦辣鸡腿堡套餐里面就没有这个操作,所以麦辣鸡腿堡套餐采用默认行为
再回顾一下,钩子的存在,可以让子类有能力对算法的不同点进行挂钩
写完,收工,继续吃汉堡去!
以上是关于汉堡中的设计模式的主要内容,如果未能解决你的问题,请参考以下文章