行为型设计模式的学习。
模板方法模式介绍
概述
李建中老师在微软有个设计模式的系列讲座,其中在讲到模板方法模式时说过这么一句:如果你只想学习一种设计模式就学习模板方法吧。由此可见它的重要性。
先来看看模板方法模式的定义:在一个方法中定义一个算法的大体框架,而将一些步骤的实现延迟到子类中,使得子类可以在不改变一个算法的结构前提下即可重定义该算法的某些特定步骤。
定义看不懂,没事,理论需要结合实践,等学习完后自己将这个设计模式实现出来后再回过头来看,你一定会恍然大悟:原来就是这样啊。
使用场景
在学习设计模式的时候,每种设计模式的使用场景才是我们最需要关注的一个重点。
如果你在写代码的过程中,遇到下面两种情况,那么就可以停下来思考是不是可以用模板方法模式了:
-
1.算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
-
2.当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
好吧,还是举个例子吧:小二哥所在的xx公司眼见BAT的某个公司游戏业务每年赚了个盆满钵满,于是也决定向游戏业进军了,由于公司老总是个麻将迷,因此公司决定开发的第一个游戏是麻将类的游戏app。
众所周知,对于麻将不同地方有不同的玩法,因此这个app,用户首先用账号登录游戏,然后可以选择玩法,最后是退出游戏。这里,登录游戏和退出游戏不区分玩法,也就是说无论哪种玩法 ,登录和退出的行为一模一样。但是具体的玩麻将的过程,不同的地方的玩法是不一样的,这个是不能复用的。这种场景就很适合用模板方法模式。
UML类图
以上面的场景为例,模板方法模式的UML类图如下:
UML 类图也比较简单,只有两个部分:
-
1.Mahjong抽象类:这个类是模板类,类中的final方法playMahjong定义了算法的骨架。
-
2.多个实现类:如HunanMahjong和GuangdongMahjong,实现类中实现了抽象类中的抽象方法,这样在执行抽象类的playMahjong时,如果playMahjong中调用了抽象方法,最终会调用对应实现类中的 该方法。
模板方法模式实现
下面是根据上面的场景用模板方法模式实现的代码:
//麻将类
abstract class Mahjong{
//final方法,定义了算法的骨架
public final void playMahjong(){
login();
play();
logout();
}
private void login(){
System.out.println("登录游戏!");
}
private void logout(){
System.out.println("退出游戏!");
}
//抽象方法,由其子类实现
public abstract void play();
}
//湖南麻将类
class HunanMahjong extends Mahjong{
@Override
public void play() {
System.out.println("湖南麻将进行中");
}
}
//广东麻将类
class GuangdongMahjong extends Mahjong{
@Override
public void play() {
System.out.println("广东麻将进行中");
}
}
public class TemplateMethodExample {
public static void main(String[] args) {
Mahjong hunanMahjong = new HunanMahjong();
hunanMahjong.playMahjong();
System.out.println();
Mahjong guangdongMahjong = new GuangdongMahjong();
guangdongMahjong.playMahjong();
}
}
******************【运行结果】******************
登录游戏!
湖南麻将进行中
退出游戏!
登录游戏!
广东麻将进行中
退出游戏!
可以看出,需要玩哪个地方的麻将,只需要实例化对应实现类的对象,然后调用抽象类的playMahjong方法即可。
好了,我们总结下模板方法模式实现的过程:
- 第一步:首先定义一个抽象的模板类Mahjong,并且定义了四个方法:模板方法playMahjong定义了整个框架;两个普通方法login, logout,子类复用,不需要子类实现;抽象放方法play,需要子类去实现,因为每个地方的玩法不一样。
- 第二步:定义具体的实体类,并实现父类的抽象方法。
模板方法模式特点
优点:提高了代码的复用度,而且很好的符合的“开闭原则”。比如说过段时间,四川麻将又很流行了,公司决定上线四川麻将,只需要用一个四川麻将类继承抽象类就可以了。
缺点:首先是类增多了;其次,模板方法模式使得程序流程变成了父类调用子类方法,使得程序比较难以理解,因为正常情况下,程序的执行流是子类调用父类的方法。
常规方式 --01
“吃饭,睡觉,打豆豆”其实都是独立的行为,为了不相互影响(比如吃饭时突然睡着了,或者睡觉时不好好睡,居然急着跑去打豆豆,开个玩笑哈~~),我们可以通过函数简单进行封装:
public class littlePenguin {
public void eating() {
System.out.println("吃饭");
}
public void sleeping() {
System.out.println("睡觉");
}
public void beating() {
System.out.println("用小翅膀打豆豆");
}
}
public class middlePenguin {
public void eating() {
System.out.println("吃饭");
}
public void sleeping() {
System.out.println("睡觉");
}
public void beating() {
System.out.println("用圆圆的肚子打豆豆");
}
}
// bigPenguin相同,省略...
public class test {
public static void main(String[] args) {
System.out.println("littlePenguin:");
littlePenguin penguin_1 = new littlePenguin();
penguin_1.eating();
penguin_1.sleeping();
penguin_1.beating();
// 下同,省略...
}
}
这样看起来,是不是要稍微清晰一些呢,工作过一段时间的同学,可能会采用这种实现方式,我们有没有更优雅的实现方式呢?
模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
这3只企鹅,由于每天吃的都一样,睡觉也都是站着睡,但是打豆豆的方式却不同,所以我们可以将“吃饭,睡觉,打豆豆”抽象出来,因为“吃饭,睡觉”都一样,所以我们可以直接实现出来,但是他们“打豆豆”的方式不同,所以封装成抽象方法,需要每个企鹅单独去实现“打豆豆”的方式。最后再新增一个方法everyDay(),固定每天的执行流程:
public abstract class penguin {
public void eating() {
System.out.println("吃饭");
}
public void sleeping() {
System.out.println("睡觉");
}
public abstract void beating();
public void everyDay() {
this.eating();
this.sleeping();
this.beating();
}
}
每只企鹅单独实现自己“打豆豆”的方式:
public class littlePenguin extends penguin {
@Override
public void beating() {
System.out.println("用小翅膀打豆豆");
}
}
public class middlePenguin extends penguin {
@Override
public void beating() {
System.out.println("用圆圆的肚子打豆豆");
}
}
public class bigPenguin extends penguin {
@Override
public void beating() {
System.out.println("拿鸡毛掸子打豆豆");
}
}
最后看调用方式:
public class test {
public static void main(String[] args) {
System.out.println("littlePenguin:");
littlePenguin penguin1 = new littlePenguin();
penguin1.everyDay();
System.out.println("middlePenguin:");
middlePenguin penguin2 = new middlePenguin();
penguin2.everyDay();
System.out.println("bigPenguin:");
bigPenguin penguin3 = new bigPenguin();
penguin3.everyDay();
}
}
“楼哥,你这代码看的费劲,能给我画一个UML图么”,“嗯,其实画图挺麻烦的,谁让楼哥是暖男呢,那我就学着给大家画一个”