设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

今天是五.四青年节,祝大家节日快乐。看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火。这两道菜大部分人都应该吃过,特别是醋溜土豆丝,作为“鲁菜”的代表作之一更是为大众所熟知,醋溜土豆丝,好吃不上火。清炒苦瓜这道菜好啊,更是夏天必备之良菜,其功效在此就不做过多赘述了。言归正传,上篇博客我们从“小弟”中学习了“外观模式”,我们也把“外观模式”戏称为“小弟模式”。今天我们要从醋溜土豆丝和清炒苦瓜的制作过程中来学习一下我们今天博客的主题“模板方法模式”(Template Method Pattern)。

说到模板方法模式,如果你看过之前发表的重构相关的博客的话,应该对模板方法模式并不陌生。在《代码重构(五):继承关系重构规则》这篇博客中第三部分,其实是使用的“模板方法模式”进行重构的,在重构规则里边我们称之为“Form Template Method (构造模板函数)”,这个模板函数就是我们本篇博客中的模板方法。今天我们要从另一个角度来看一下“模板方法模式”,并从“醋溜土豆丝”和“清炒苦瓜”的制作实例中来学习一下“模板方法模式”。在本篇博客中,你不仅是一位Programer,还是一位Cook。

老规矩,在博客的开头,我们先给出“模板方法模式”的定义。如果你是小白,定义看不懂没关系,因为定义一般都比较难理解。你可以看完下方的具体实例后在回头看这个定义即可。模板方法的定义如下:

模板方法模式:在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

一、开始炒菜

接下来我们将要开始制作我们的醋溜土豆丝和清炒苦瓜这两道菜,当然在本篇博客的第一部分我们不会使用我们的模板方法来炒菜的。我们在该部分先给出常规的做菜的方法,然后我们会对其分析,最终会使用我们的“模板方法模式”来进行炒菜。当然在此我们不是真的用锅炒菜了,而是用我们的代码来炒菜,走起。

1.炒一个醋溜土豆丝

接下来我们要炒一个醋溜土豆丝,醋溜土豆丝好吃,做起来又简单。还是那句话,醋溜土豆丝,好吃不上火。因为炒“醋溜土豆丝”我们是在一个类中完成的,所以在此就不画类图了,等下发使用模板方法模式后,在给出相应的类图。下方的FryShreddedPotatoes类就是我们做“醋溜土豆丝”的类。其中给出了做醋溜土豆丝的一系列的步骤,并且在fryShreddedPotatoes()函数中对这一系列的函数进行了组合。

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

上面完成的这个类,我们就可以将上述这个类进行实例化并调用fryShreddedPotatoes()来炒一盘醋溜土豆丝了。下方我们就将FryShreddedPotatoes进行实例化,然后炒了一盘醋溜土豆丝。

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

2、来一盘“清炒苦瓜”

上面土豆丝炒完了,已出锅。接下来我们还要来一盘“清炒苦瓜”,当然“清炒苦瓜”的做法和上面“醋溜土豆丝”差不过,不过有些步骤还是不同的。当然你在炒苦瓜的时候不能放醋了,还有在炒苦瓜的时候你放的是苦瓜片而不是土豆丝了,这些都是不同的。当然两者的菜名也不一样呢。下方这个类就是我们“清炒苦瓜”的类,你可以将该类进行实例化然后去做一盘属于你的清炒苦瓜。大体上一看,下方的代码与上面我们醋溜土豆丝的类的步骤差不多,但是具体细节,以及一些步骤所调用的方法所不同。下方就是我们清炒苦瓜的类,如下所示:

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

接着就是来实例化上面“清炒苦瓜”的类,然后来一盘清炒苦瓜。下方是我们创建的上面的类的对象,然后去调用炒苦瓜的方法。下方是调用方式和输出结果,经过下方的过程,我们就可以出锅一盘清炒苦瓜了,如下所示:

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

二、使用“模板方法”来炒菜

在上面两种炒菜的方法中,我们不难看出炒醋溜土豆丝和清炒苦瓜中的类中有好多重复的代码。在我们重构系列中的博客中我们已经提到切记重复代码,所以我们要对上面的两个类进行代码的重构,而重构的方式就是使用“模板方法模式”来进行重构。本质上就是将变的部分与不变的部分进行分离,在该实例中变的部分就是某些步骤的具体细节,而不变的是执行的步骤和部分步骤中的内容。

在该炒菜实例中整个炒菜的流程是不变的,该流程中的一些步骤也是不变的。变化的就是报菜名方法中所报的菜名不同,然后是所炒的菜不同,一个炒的是土豆丝一个炒的是苦瓜,最后是加的调料不同。这样一分析,我们可以将不变的方法放到父类中,将炒菜这个不变的步骤封装成“模板方法”,具体不变的某些步骤(比如放油等)可以放在延展中实现。该部分的代码结构些微的有些复杂,所以我们会给出相应的类图。

1.重构后的类图如下(使用了“模板方法模式”)

首先我们会给出重构后的类图,使用“模板方法模式”重构后的类图如下所示。 FryVegetablesType是我们创建的炒菜协议,其中定义了“醋溜土豆丝”和“清炒苦瓜”所有的方法,不过我们对两者不同的方法进行了重新命名,让其统一。因为两者不同的方法所实现的东西大体一致,比如之前清炒苦瓜中的放苦瓜的方法putBitterGourd(),我们重命名成了“放蔬菜”的putVegetables()方法。putVegetables()方法用于“醋溜土豆丝”中也是可以的,因为putVegetables()在“清炒苦瓜”中放的是苦瓜,在炒土豆丝中放的是土豆丝。

在FryVegetablesType协议的延展中,我们给出了相同的默认实现,比如模板方法(fry())、放油(putSomeOil())、放葱花(putSomeGreenOnion())、出锅(outOfThePan())等方法。在炒这两个菜时延展中的方法是不变的。而我们炒土豆丝和炒苦瓜中实现的方法是两者不同的地方,类图如下:

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

2.炒菜接口与接口延展代码实现

根据上述的类图,我们可以给出炒菜接口以及接口延展的代码实现。接下来要做的事情就是将不变的部分提取到接口和接口的延展中,下方就是我们提取的炒菜的接口FryVegetablesType以及该接口对应的延展。FryVegetablesType协议中给出了模板方法fry(),以及炒菜的步骤(也就是炒菜的算法)。在该接口的延展中,模板方法fry()的默认实现调用和这些步骤,并且给出了一些不变的步骤的实现。具体代码如下所示:

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

3.“醋溜土豆丝”和“清炒苦瓜”的具体实现

下方代码段是醋溜土豆丝和清炒苦瓜在模板方法模式中的代码实现。从下方代码我们不难看出,两者都遵循了FryVegetablesType协议,并且拥有该协议的默认扩展。我们知道默认扩展中的代码类似于抽象类的默认实现,为子类所共有,所以下方两个类只给出了不同的步骤。比如在醋溜土豆丝中放的作料是盐和醋,而在清炒苦瓜中放的作料是盐。当然两个子类中其他实现的两个方法也是不同之处。无论子类怎么给出默认实现的步骤,我们在默认延展中给出的模板方法是不变的,也就是炒菜的具体步骤是不变的。这就是模板方法模式,模板方法不关心每个步骤的具体细节,只关心步骤执行的顺序,这就是所谓的模板方法是对算法的封装,而不是对具体计算细节的封装。

使用模板方法的一个显而易见的好处就是减少了代码冗余,将变化的部分与不变的部分进行了分离。在该示例中就是将不变的部分放在了协议的默认延展中,将变化的部分放在了子类中。这就是“模板方法模式”。

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

4、“模板方法模式”的测试用例

接下来我们要对上述的示例进行测试,下方就是我们模板方法的测试用例,其实下方的测试用例与之前没有使用模板方法时的测试用例类似,只是炒菜调用的方法有所不同。虽然调用的方法有些差异,此处的差异仅仅是函数名称的差异,该函数所做的事情没有变化。下方就是我们重构后的代码的测试用例以及运行结果。从结果中看出,与我们之前没有使用模板方法的测试用例的输出结果一致。这就是我们之前在“重构”系列博客中经常提到的改变代码内部的结构,而不改变代码对外调用的接口。

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

本篇博客与之前我们类重构中的“构建模板方法”的部分较为类似,都是介绍的模板方法模式。而在前面我们是从重构的角度来使用模板方法模式的,而今天的博客的主题不是重构而是我们的“模板方法模式”。由于篇幅有限,我们的今天的博客就先到这儿,后面还会继续更新其他Swift版的设计模式。

今天博客中的代码在github上的分享地址为:https://github.com/lizelu/DesignPatterns-Swift

上一篇:阿里与腾讯“智慧城市”的O2O谁更强?(分享)


下一篇:24小时学通Linux内核之电源开和关时都发生了什么