模板方法模式(行为型)

前言


在找工作写简历的时候,会发现网上有许多简历模板,这些简历模板都包含一些了解一个求职者的基本信息,包括个人基本信息,教育经历,工作经历等等,还有些非英语专业毕业的还需要包括CET的成绩,若英语专业则不需要。像这种简历模板只提供一个基本的模板,而具体的内容由具体的使用者(具体子类)实现的模式可以通过模板方式模式来实现。

模板方法模式


模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式
模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些基本方法,从而使得相同的算法框架可以有不同的执行结果

意图

定义了操作算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

参与者

  • AbstractClass
    抽象类,定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法
    的各个步骤
    实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作

  • ConcreteClass
    具体类,实现原语操作(primitive operation)以完成算法中与特定子类相关的步骤

模板方法:定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法,模板方法是一个具体方法。它给出了一个顶层逻辑框架,而逻辑的组成步骤在抽象类中可以是具体方法,也可以是抽象方法。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口
基本方法:基本方法是实现算法各个步骤的方法,是模板方法的组成部分,分为三类
1.抽象方法:抽象类声明、由其具体子类实现
2.具体方法:个具体方法由一个抽象类或具体类声明并实现,其子类可以进行重写也可以直接继承
3.钩子方法:由一个抽象类或具体类声明并实现,而其子类可能会加以扩展,一般来说有两种钩子方法,一种是父类是空的实现钩子方法,子类通过重写该方法来控制父类的流程;另一种是父类是个返回bool型的钩子方法,一般有个默认值,而子类通过重写该bool返回值的钩子方法,来控制父类的流程以实现在不同条件下执行模板方法中的不同步骤

模式结构

template

代码实现

1.首先定义抽象类AbstractFrameWork,声明和实现模板方法SkeletonOperator(),并声明或实现相应的原语操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 骨架流程类
class AbstractFrameWork
{
public: // 声明为public 客户端调用

// 模板方法
void SkeletonOperator()
{
PrimitiveOperator1();
PrimitiveOperator2();
PrimitiveOperator3();
if (isSupportPrimitiveOperator4())
{
PrimitiveOperator4();
}
PrimitiveOperator5();
}

protected:
// 声明为protected 客户端不可见,子类可见, 也可以声明为private 对于virtual而言即使private属性也有多态

//父类抽象虚拟方法,必须被子类实现
virtual void PrimitiveOperator1() = 0;
virtual void PrimitiveOperator2() = 0;

// 父类具体普通方法,有默认实现,可以被子类覆盖(是有条件的,必须面对具体编程,且显式的直接调用PrimitiveOperator3,不能是模板方法SkeletonOperato)
//最好是加virtual 子类就重写了该方法
void PrimitiveOperator3()
{
cout << "PrimitiveOperator3 Default Implement" << endl;
// 此时若在客户端通过基类指针来指向具体的子类对象(通过面对抽象编程),此时不管指向哪个具体子类对象,调用的均是父类方法
因为没有virtual,没有多态,是静态绑定的; 若想调用子类对象的方法,必须要面对具体对象编程,通过子类指针来指向子类对象,且显式的直接调用该方法
}

//父类具体虚拟方法,有默认实现,可以被子类重写
virtual void PrimitiveOperator4()
{
cout << "PrimitiveOperator4 Default Implement" << endl;
}

// 父类具体钩子虚拟方法(默认是空的),可以被子类重写,通过子类重写来控制父类的流程
virtual void PrimitiveOperator5()
{
}
// 父类具体钩子虚拟方法(返回bool类型),可以被子类重写,通过子类重写来控制父类的流程
virtual bool isSupportPrimitiveOperator4()
{
return true;
}
};

2.再定义三个具体的实现类ConcreteImplementAConcreteImplementBConcreteImplementC,并实现或重写相应的原语操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//ConcreteImplementA
class ConcreteImplementA : public AbstractFrameWork
{
public:
void PrimitiveOperator1()
{
cout << "ConcreteImplement A Primitive Operator1" << endl;
}
void PrimitiveOperator2()
{
cout << "ConcreteImplement A Primitive Operator2" << endl;
}
void PrimitiveOperator3() //覆盖了父类默认的普通方法PrimitiveOperator3,但是面对抽象编程时候子类该方法不会生效,除非父类该方法加virtual
{
cout << "ConcreteImplement A Primitive Operator3" << endl;
}
void PrimitiveOperator5() // 重写了父类默认为空的钩子方法PrimitiveOperator5
{
cout << "ConcreteImplement A Primitive Operator5" << endl;
}
};

//ConcreteImplementB
class ConcreteImplementB : public AbstractFrameWork
{
public:
void PrimitiveOperator1()
{
cout << "ConcreteImplement B Primitive Operator1" << endl;
}
void PrimitiveOperator2()
{
cout << "ConcreteImplement B Primitive Operator2" << endl;
}
bool isSupportPrimitiveOperator4() // 重写了父类默认返回true的钩子方法isSupportPrimitiveOperator4
{
return false;
}
};

////ConcreteImplementC
class ConcreteImplementC : public AbstractFrameWork
{
public:
void PrimitiveOperator1()
{
cout << "ConcreteImplement C Primitive Operator1" << endl;
}
void PrimitiveOperator2()
{
cout << "ConcreteImplement C Primitive Operator2" << endl;
}
void PrimitiveOperator3() //覆盖了父类默认的普通方法PrimitiveOperator3,但是面对抽象编程时候子类该方法不会生效,除非父类该方法加virtual
{
cout << "ConcreteImplement C Primitive Operator3" << endl;
}
void PrimitiveOperator4() //重写了父类默认的虚方法PrimitiveOperator4
{
cout << "ConcreteImplement C Primitive Operator4" << endl;
}
};

3.测试模板方法模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void TemplateTest_General()
{
// 面对抽象编程
AbstractFrameWork *pCI_A = new ConcreteImplementA();
pCI_A->SkeletonOperator();

AbstractFrameWork *pCI_B = new ConcreteImplementB();
pCI_B->SkeletonOperator();

AbstractFrameWork *pCI_C = new ConcreteImplementC();
pCI_C->SkeletonOperator();

// 面对具体编程
ConcreteImplementA *pC_A = new ConcreteImplementA();
pC_A->SkeletonOperator(); // 没有覆盖父类的方法,仍然调用的是父类的PrimitiveOperator3 Default Implement
pC_A->PrimitiveOperator3(); // 覆盖了父类的方法:ConcreteImplement A Primitive Operator3
ConcreteImplementC *pC_C = new ConcreteImplementC();
pC_C->SkeletonOperator(); // 没有覆盖父类的方法,仍然调用的是父类的PrimitiveOperator3 Default Implement
pC_C->PrimitiveOperator3(); // 覆盖了父类的方法:ConcreteImplement C Primitive Operator3

SAFE_RELASE_POINTER(pCI_A);
SAFE_RELASE_POINTER(pCI_B);
SAFE_RELASE_POINTER(pCI_C);
SAFE_RELASE_POINTER(pC_A);
SAFE_RELASE_POINTER(pC_C);
}

4.运行结果:

ConcreteImplement A Primitive Operator1
ConcreteImplement A Primitive Operator2
PrimitiveOperator3 Default Implement
PrimitiveOperator4 Default Implement
ConcreteImplement A Primitive Operator5
ConcreteImplement B Primitive Operator1
ConcreteImplement B Primitive Operator2
PrimitiveOperator3 Default Implement
ConcreteImplement C Primitive Operator1
ConcreteImplement C Primitive Operator2
PrimitiveOperator3 Default Implement
ConcreteImplement C Primitive Operator4
ConcreteImplement A Primitive Operator1
ConcreteImplement A Primitive Operator2
PrimitiveOperator3 Default Implement
PrimitiveOperator4 Default Implement
ConcreteImplement A Primitive Operator5
ConcreteImplement A Primitive Operator3
ConcreteImplement C Primitive Operator1
ConcreteImplement C Primitive Operator2
PrimitiveOperator3 Default Implement
ConcreteImplement C Primitive Operator4
ConcreteImplement C Primitive Operator3

与策略模式的区别

策略模式是对象行为型模式,是通过委托对象来达到复用目的;而模板方法模式是类行为型模,是通过继承来实现复用的

使用场景

  • 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。即一次性实现一个算法的不变部分,并将可变的行为留给子类来实现
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制

优缺点

  • 优点
    • 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序
    • 是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用
    • 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
    • 可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则
  • 缺点
    • 如果父类中可变的基本方法太多,将会导致类的个数增加,系统过于庞大

模板方法模式具体实例


简历模板问题

使用模板方法模式简单实现前言所述的简历模板问题

代码实现
1.定义简历模板类Resume,并声明和实现模板方法WriteResume():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 简历模板
class Resume
{
public: // public,供客户端调用
void WriteResume() // 模板方法
{
WritePersonalInfo();
WriteEnducationExp();
WriteWorkExp();
if (isNotEnglishMajor())
{
WriteCETScore();
}
WritePublishedPaper();
}
protected: // protected
virtual void WritePersonalInfo() = 0;
virtual void WriteEnducationExp() = 0;
virtual void WriteWorkExp() = 0;
virtual bool isNotEnglishMajor() //钩子方法
{
return true;
}
virtual void WriteCETScore() = 0;
virtual void WritePublishedPaper() //钩子方法
{
}
};

2.定义两个种具体的简历类ResumeAResumeB,并实现或重写相应的原语操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class ResumeA : public Resume
{
public:
void WritePersonalInfo()
{
cout << "My Name is A" << endl;
}
void WriteEnducationExp()
{
cout <<"I Graduated from HUST" << endl;
}
void WriteWorkExp()
{
cout << "I Worked in ZTE" << endl;
}
void WriteCETScore()
{
cout << "CET4 is 550 score" << endl;
}
};

class ResumeB : public Resume
{
public:
void WritePersonalInfo()
{
cout << "My Name is B" << endl;
}
void WriteEnducationExp()
{
cout <<"I Graduated from WuHan University" << endl;
}
void WriteWorkExp()
{
cout << "I Worked in HW" << endl;
}
bool isNotEnglishMajor()
{
return false;
}
void WriteCETScore() // 必须实现,否则编译不过,不能实例化抽象类,此处为空实现
{
}
void WritePublishedPaper()
{
cout << "I have published a paper about program learning " << endl;
}
};

3.测试模板方法模式:

1
2
3
4
5
6
7
8
9
10
11
void TemplateTest_Resume()
{
Resume *pR_A = new ResumeA();
pR_A->WriteResume();

Resume *pR_B = new ResumeB();
pR_B->WriteResume();

SAFE_RELASE_POINTER(pR_A);
SAFE_RELASE_POINTER(pR_B);
}

4.运行结果:

My Name is A
I Graduated from HUST
I Worked in ZTE
CET4 is 550 score
My Name is B
I Graduated from WuHan University
I Worked in HW
I have published a paper about program learning