汉堡中的设计模式

Posted 码农Amg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了汉堡中的设计模式相关的知识,希望对你有一定的参考价值。

艺术来源于生活,有时候灵感真的就在那么一瞬间

看到上图这个板烧没有,这就是我今晚的晚餐了;走进麦当劳里面,有很多很多的汉堡

板烧鸡腿堡、麦辣鸡腿堡、麦香堡、深海鳕鱼堡....

这些个汉堡的制作方式,似乎有着一种很明显的相似感,汉堡的制作方式非常像之前学习过的一种设计模式,今天我们就来聊一聊

我们不妨更加仔细的思考一下上面的问题

  • 我要做一个【板烧鸡腿堡】
    1. 取出两块面包,烤热
    2. 放上烤热的腌制好的鸡腿肉,撒上独特的酱汁
    3. 撒上蔬菜
    4. 组合成汉堡
    5. 装在纸袋里面
  • 我要做一个【麦辣鸡腿堡】
    1. 取出两块面包,烤热
    2. 放上炸制好的鸡腿肉,撒上独特的酱汁
    3. 撒上蔬菜
    4. 组合成汉堡
    5. 装在纸袋里面
  • 我要做一个【新奥尔良烤鸡腿堡】
    1. 取出两块面包,烤热
    2. 放上新奥尔良鸡腿肉,撒上独特的酱汁
    3. 撒上蔬菜
    4. 组合成汉堡
    5. 装在纸袋里面
  • 制作其他汉堡......

有图有真相

可以发现,除了步骤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;而在麦辣鸡腿堡套餐里面就没有这个操作,所以麦辣鸡腿堡套餐采用默认行为

再回顾一下,钩子的存在,可以让子类有能力对算法的不同点进行挂钩

写完,收工,继续吃汉堡去!

以上是关于汉堡中的设计模式的主要内容,如果未能解决你的问题,请参考以下文章

选择后如何关闭汉堡菜单? javascript

在 Android 中创建带有导航抽屉的汉堡菜单

应用ActionBar中的返回按钮

NavigationDrawer - 每个片段的不同工具栏

创建型模式---建造者模式

何时使用汉堡包菜单设计