在模板方法模式(Template Method Pattern)中,定义一个操作中的算法的框架,而将一些步骤的执行延迟到子类中,使得子类可以在不改变算法的结构的前提下即可重新定义该算法的某些特定步骤。
通常一个算法需要几个执行步骤来实现,而有时我们需要定义几种执行步骤一致,但是却可能在某个步骤的实现略有差异的算法。也就是说我们既需要复用实现相同的步骤,也可以通过在某个步骤的不同实现来灵活扩展出更多不同的算法。
在这种场景下,我们可以使用模板方法模式:定义好一个算法的框架,在父类实现可以复用的算法步骤,而将需要扩展和修改其他步骤的任务推迟给子类进行。
现在我们清楚了模板方法模式的适用场景,下面看一下这个模式的成员和类图。
模板方法模式的成员除了客户端以外,只有两个成员:
有些参考资料定义这两个成员为
Abstract Class
和Concrete Class
。
下面通过类图来看一下命令模式各个成员之间的关系:
模板方法模式类图
由上图可以看出,Algorithm
的excute
方法是算法接口,它在内部调用了三个步骤方法:step1
,step2
,step3
。而step2
是未暴露在外部的,因为这个步骤是需要各个子类复用的。因此Algorithm
只将step1
和step3
暴露了出来以供子类来调用。
模拟一个制作三种热饮的场景:热美式咖啡,热拿铁,热茶。
这三种热饮的制作步骤是一致的,都是三个步骤:
虽然制作步骤是一致的,但是不同种类的热饮在每一步可能是不同的:咖啡和茶叶主成分是咖啡粉和茶叶;而辅助成分:美式咖啡和茶叶可以不添加,而拿铁还需添加牛奶。
而第一步是相同的:准备热水。
根据上面对模板方法模式的介绍,像这样算法步骤相同,算法步骤里的实现可能相同或不同的场景我们可以使用模板方法模式。下面我们看一下如何用代码来模拟该场景。
首先我们创建算法类HotDrink
:
//================== HotDrink.h ==================
@interface HotDrink : NSObject
- (void)makingProcess;
- (void)addMainMaterial;
- (void)addIngredients;
@end
//================== HotDrink.m ==================
@implementation HotDrink
- (void)makingProcess{
NSLog(@" ===== Begin to making %@ ===== ", NSStringFromClass([self class]));
[self boilWater];
[self addMainMaterial];
[self addIngredients];
}
- (void)prepareHotWater{
NSLog(@"prepare hot water");
}
- (void)addMainMaterial{
NSLog(@"implemetation by subClasses");
}
- (void)addIngredients{
NSLog(@"implemetation by subClasses");
}
@end
HotDrink
向外部暴露了一个制作过程的接口makingProcess
,这个接口内部调用了热饮的所有制作步骤方法:
- (void)makingProcess{
//准备热水
[self prepareHotWater];
//添加主成分
[self addMainMaterial];
//添加辅助成分
[self addIngredients];
}
HotDrink
只向外暴露了这三个步骤中的两个需要子类按照自己方式实现的接口:
//添加主成分
- (void)addMainMaterial;
//添加辅助成分
- (void)addIngredients;
因为热饮的第一步都是一致的(准备热水),所以第一步骤的接口没有暴露出来给子类实现,而是直接在当前类实现了,这也就是模板方法的一个可以复用代码的优点。
OK,我们现在创建好了算法类,那么根据上面的需求,我们接着创建三个具体算法类:
HotDrinkTea
:热茶HotDrinkLatte
:热拿铁HotDrinkAmericano
:热美式//================== HotDrinkTea.h ==================
@interface HotDrinkTea : HotDrink
@end
//================== HotDrinkTea.m ==================
@implementation HotDrinkTea
- (void)addMainMaterial{
NSLog(@"add tea leaf");
}
- (void)addIngredients{
NSLog(@"add nothing");
}
@end
热茶在addMainMaterial
步骤里面是添加了茶叶,而在addIngredients
步骤没有做任何事情(这里先假定是纯的茶叶)。
类似地,我们看一下两种热咖啡的实现。首先是热拿铁HotDrinkLatte
:
//================== HotDrinkLatte.h ==================
@interface HotDrinkLatte : HotDrink
@end
//================== HotDrinkLatte.m ==================
@implementation HotDrinkLatte
- (void)addMainMaterial{
NSLog(@"add ground coffee");
}
- (void)addIngredients{
NSLog(@"add milk");
}
@end
热拿铁在addMainMaterial
步骤里面是添加了咖啡粉,而在addIngredients
步骤添加了牛奶。
下面再看一下热美式HotDrinkAmericano
:
//================== HotDrinkAmericano.h ==================
@interface HotDrinkAmericano : HotDrink
@end
//================== HotDrinkAmericano.m ==================
@implementation HotDrinkAmericano
- (void)addMainMaterial{
NSLog(@"add ground coffee");
}
- (void)addIngredients{
NSLog(@"add nothing");
}
@end
热美式在addMainMaterial
步骤里面是添加了咖啡粉,而在addIngredients
步骤没有做任何事,因为美式就是纯的咖啡,理论上除了水和咖啡不需要添加任何其他东西。
到现在三种热饮类创建好了,我们现在分别制作这三种热饮,并看一下日至输出:
===== Begin to making HotDrinkTea =====
prepare hot water
add tea leaf
add nothing
===== Begin to making HotDrinkLatte =====
prepare hot water
add ground coffee
add milk
===== Begin to making HotDrinkAmericano =====
prepare hot water
add ground coffee
add nothing
上面的日至输出准确无误地反映了我们所定义的这三种热饮制作过程:
下面看一下上面代码对应的类图。
模板方法模式代码示例类图
UIView
的drawRect:
方法可以自定义绘图,是模板方法模式的一种实践。java.lang.Runnable
是使用JDK的经典场景:Runnable
接口可以作为抽象的命令,而实现了Runnable的线程即是具体的命令。