在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
在英雄联盟中备受关注的人物莫过于盲僧了。作为一个刺客,有着飘逸的技能,今儿,就在这里讨论一下盲僧开团的连招。
盲僧开团的连招可以用多种方式来完成,但归根结底就是要将对手踢回到友军身边。要达到这个目的,首先需要接近目标,然后要移动到目标的身后方,最后使用R技能神龙摆尾使目标发生位移。既然基本的流程已经定了下来,那最好使用模版方法模式来完成设计,毕竟我们不想定下来的流程有任何改变。
首先来看一下类图:
我们使用抽象类将整个连招的流程梳理出来,虽然流程已经定了下来,但是某些步骤的细节却各不相同,因此我们将nearTarget
与toBackside
方法定义成抽象方法,子类可以在里面实现自己的细节。
部分实现代码:
@implementation BlindMonkContinueSkill
// 对外公开的连招方法,这里定义了连招的流程
- (void)continueSkill {
[self nearTarget];
[self toBackside];
[self kickback];
}
- (void)kickback {
NSLog(@"使用神龙摆尾将目标踢回来");
}
- (void)nearTarget { NSAssert(YES, @"抽象方法"); }
- (void)toBackside { NSAssert(YES, @"抽象方法"); }
@end
简单的说,模版方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
现在子类可以去实现“靠近目标”与“到目标背后”这些细节方法了:
// 第一个盲僧的连招细节
@implementation BlindMonk_1
- (void)nearTarget {
NSLog(@"%@使用回音击接近目标", NSStringFromClass(self.class));
}
- (void)toBackside {
NSLog(@"使用金钟罩到目标背后");
}
@end
// 第二个盲僧的连招细节
@implementation BlindMonk_2
- (void)nearTarget {
NSLog(@"%@使用金钟罩接近目标", NSStringFromClass(self.class));
}
- (void)toBackside {
NSLog(@"使用闪现到目标背后");
}
@end
最后我们调用continueSkill
方法进行连招:
BlindMonk_1 *blindMonk1 = [[BlindMonk_1 alloc] init];
[blindMonk1 continueSkill];
NSLog(@"------------分割线--------------");
BlindMonk_2 *blindMonk2 = [[BlindMonk_2 alloc] init];
[blindMonk2 continueSkill];
使用模版方法带来的好处大致如下:
- 避免了重复的代码实现,比如
kickback
方法 - 有效的保护了整个连招流程
- 整个连招流程只在一个地方出现,所以更容易维护
- 提供了一个框架,使得各种各样的盲僧可以容易的接进来,他们只需要实现自己的细节方法便可
细心的观众可能发现了在抽象类中有一个leaveBattlefield
方法,这就是传说中的钩子,钩子是被声明在抽象类中的方法,可以让子类有能力对整个连招流程的不同点进行挂钩。下面介绍钩子的一种用法:
/* 抽象类BlindMonkContinueSkill */
- (void)continueSkill {
[self nearTarget];
[self toBackside];
[self kickback];
// 进行挂钩处理
if ([self leaveBattlefield]) {
[self leave];
}
}
// 钩子,子类可以选择性的覆盖此方法
// 默认返回YES,表示离开战场
- (BOOL)leaveBattlefield {
return YES;
}
/* 在类BlindMonk_2中返回NO,表示不离开战场 */
-(BOOL)leaveBattlefield {
return NO;
}
这样,子类就可以控制在整个连招中是否执行某些步骤。测试结果如下:
点击这里获取完成代码
并不是所有的盲僧都瞎,你瞧BlindMonk_1,一顿飘逸操作后,还能潇洒离开战场。
最后,你可能觉着模版方法模式与策略模式有些相似,其实他们还是有区别的:前者使用继承,后者使用组合。还有,之前提到的工厂方法,其实就是模版方法的特殊版本。
倾情告白:模版方法模式由超类主控一切,当有需要的时候,自然会去调用子类。另外,钩子可以让子类实现流程中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以忽略钩子。钩子的另一个用法是让子类有机会对模版方法中某些即将发生的(或刚刚发生的)步骤作出反应。例如viewWillAppear:
等一系列方法。
关注微信公众号CodingArtist,可以第一时间得到文章更新通知! _