设计模式:C++ 模板方法模式 {Template Method in C++}
- [1. Solution](#1. Solution)
- [2. Structure](#2. Structure)
- [3. Applicability (模板方法模式适合应用场景)](#3. Applicability (模板方法模式适合应用场景))
- [4. Implement](#4. Implement)
- [5. Pros and Cons (模板方法模式优缺点)](#5. Pros and Cons (模板方法模式优缺点))
- [6. Relations with Other Patterns](#6. Relations with Other Patterns)
- [7. The Template Method design pattern](#7. The Template Method design pattern)
- References
C++ 模板方法
https://refactoringguru.cn/design-patterns/template-method/cpp/example
https://refactoring.guru/design-patterns/template-method
Template Method in C++
https://refactoring.guru/design-patterns/template-method/cpp/example
https://refactoring.guru/design-patterns/singleton
Template Method is a behavioral design pattern that allows you to define a skeleton of an algorithm in a base class and let subclasses override the steps without changing the overall algorithm's structure.
模版方法是一种行为设计模式,它在基类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
The Template Method pattern is quite common in C++ frameworks. Developers often use it to provide framework users with a simple means of extending standard functionality using inheritance.
模版方法模式在 C++ 框架中很常见。开发者通常使用它来向框架用户提供通过继承实现的、对标准功能进行扩展的简单方式。
Template Method can be recognized if you see a method in base class that calls a bunch of other methods that are either abstract or empty.
模版方法可以通过行为方法来识别,该方法已有一个在基类中定义的 "默认" 行为。
Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
模板方法模式是一种行为设计模式,它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。
1. Solution
The Template Method pattern suggests that you break down an algorithm into a series of steps, turn these steps into methods, and put a series of calls to these methods inside a single template method. The steps may either be abstract, or have some default implementation. To use the algorithm, the client is supposed to provide its own subclass, implement all abstract steps, and override some of the optional ones if needed (but not the template method itself).
模板方法模式建议将算法分解为一系列步骤,然后将这些步骤改写为方法,最后在 "模板方法" 中依次调用这些方法。步骤可以是抽象的,也可以有一些默认的实现。为了能够使用算法,客户端需要自行提供子类并实现所有的抽象步骤。如有必要还需重写一些步骤 (但这一步中不包括模板方法自身)。
As you can see, we've got two types of steps:
- abstract steps must be implemented by every subclass
抽象步骤必须由各个子类来实现 - optional steps already have some default implementation, but still can be overridden if needed
可选步骤已有一些默认实现,但仍可在需要时进行重写
There's another type of step, called hooks. A hook is an optional step with an empty body. A template method would work even if a hook isn't overridden. Usually, hooks are placed before and after crucial steps of algorithms, providing subclasses with additional extension points for an algorithm.
还有另一种名为钩子的步骤。钩子是内容为空的可选步骤。即使不重写钩子,模板方法也能工作。钩子通常放置在算法重要步骤的前后,为子类提供额外的算法扩展点。
2. Structure
The Abstract Class declares methods that act as steps of an algorithm, as well as the actual template method which calls these methods in a specific order. The steps may either be declared abstract or have some default implementation.
抽象类 (Abstract Class) 会声明作为算法步骤的方法,以及依次调用它们的实际模板方法。算法步骤可以被声明为抽象类型,也可以提供一些默认实现。
Concrete Classes can override all of the steps, but not the template method itself.
具体类 (Concrete Classes) 可以重写所有步骤,但不能重写模板方法自身。
3. Applicability (模板方法模式适合应用场景)
Use the Template Method pattern when you want to let clients extend only particular steps of an algorithm, but not the whole algorithm or its structure.
当你只希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可使用模板方法模式。
The Template Method lets you turn a monolithic algorithm into a series of individual steps which can be easily extended by subclasses while keeping intact the structure defined in a superclass.
模板方法将整个算法转换为一系列独立的步骤,以便子类能对其进行扩展,同时还可让超类中所定义的结构保持完整。
Use the pattern when you have several classes that contain almost identical algorithms with some minor differences. As a result, you might need to modify all classes when the algorithm changes.
当多个类的算法除一些细微不同之外几乎完全一样时,你可使用该模式。 但其后果就是,只要算法发生变化,你就可能需要修改所有的类。
When you turn such an algorithm into a template method, you can also pull up the steps with similar implementations into a superclass, eliminating code duplication. Code that varies between subclasses can remain in subclasses.
在将算法转换为模板方法时,你可将相似的实现步骤提取到超类中以去除重复代码。子类间各不同的代码可继续保留在子类中。
4. Implement
-
Analyze the target algorithm to see whether you can break it into steps. Consider which steps are common to all subclasses and which ones will always be unique.
分析目标算法,确定能否将其分解为多个步骤。从所有子类的角度出发,考虑哪些步骤能够通用,哪些步骤各不相同。
-
Create the abstract base class and declare the template method and a set of abstract methods representing the algorithm's steps. Outline the algorithm's structure in the template method by executing corresponding steps. Consider making the template method
finalto prevent subclasses from overriding it.创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。在模板方法中根据算法结构依次调用相应步骤。可用 final 最终修饰模板方法以防止子类对其进行重写。
-
It's okay if all the steps end up being abstract. However, some steps might benefit from having a default implementation. Subclasses don't have to implement those methods.
虽然可将所有步骤全都设为抽象类型,但默认实现可能会给部分步骤带来好处,因为子类无需实现那些方法。
-
Think of adding hooks between the crucial steps of the algorithm.
可考虑在算法的关键步骤之间添加钩子。
-
For each variation of the algorithm, create a new concrete subclass. It must implement all of the abstract steps, but may also override some of the optional ones.
为每个算法变体新建一个具体子类,它必须实现所有的抽象步骤,也可以重写部分可选步骤。
5. Pros and Cons (模板方法模式优缺点)
You can let clients override only certain parts of a large algorithm, making them less affected by changes that happen to other parts of the algorithm.
你可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小。
You can pull the duplicate code into a superclass.
你可将重复代码提取到一个超类中。
You might violate the Liskov Substitution Principle by suppressing a default step implementation via a subclass.
通过子类抑制默认步骤实现可能会导致违反里氏替换原则。
Template methods tend to be harder to maintain the more steps they have.
模板方法中的步骤越多, 其维护工作就可能会越困难。
6. Relations with Other Patterns
Factory Method is a specialization of Template Method. At the same time, a Factory Method may serve as a step in a large Template Method.
工厂方法模式是模板方法模式的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。
Template Method is based on inheritance: it lets you alter parts of an algorithm by extending those parts in subclasses. Strategy is based on composition: you can alter parts of the object's behavior by supplying it with different strategies that correspond to that behavior. Template Method works at the class level, so it's static. Strategy works on the object level, letting you switch behaviors at runtime.
模板方法基于继承机制:它允许你通过扩展子类中的部分内容来改变部分算法。策略模式基于组合机制:你可以通过对相应行为提供不同的策略来改变对象的部分行为。模板方法在类层次上运作,因此它是静态的。策略在对象层次上运作,因此允许在运行时切换行为。
7. The Template Method design pattern
#include <iostream>
#include <string>
/*
* The Abstract Class defines a template method that contains a skeleton of some
* algorithm, composed of calls to (usually) abstract primitive operations.
*
* Concrete subclasses should implement these operations, but leave the template
* method itself intact.
*/
class AbstractClass {
public:
/*
* Virtual destructor is crucial for polymorphic base classes to ensure
* derived objects are deleted correctly, preventing memory leaks.
*/
virtual ~AbstractClass() = default;
/*
* The template method defines the skeleton of an algorithm.
* Marked as 'final' to prevent subclasses from overriding the algorithm's structure.
*/
void template_method() const {
this->base_operation1();
this->required_operation1();
this->base_operation2();
this->hook1();
this->required_operation2();
this->base_operation3();
this->hook2();
}
protected:
/*
* These operations already have default implementations.
*/
void base_operation1() const {
std::cout << "AbstractClass: base_operation1()\n";
}
void base_operation2() const {
std::cout << "AbstractClass: base_operation2()\n";
}
void base_operation3() const {
std::cout << "AbstractClass: base_operation3()\n";
}
/*
* These operations have to be implemented in subclasses.
*/
virtual void required_operation1() const = 0;
virtual void required_operation2() const = 0;
/*
* These are "hooks." Subclasses may override them, but it's not mandatory
* since the hooks already have default (but empty) implementation. Hooks
* provide additional extension points in some crucial places of the algorithm.
*/
virtual void hook1() const {}
virtual void hook2() const {}
};
/*
* Concrete classes have to implement all abstract operations of the base class.
* They can also override some operations with a default implementation.
*/
class ConcreteClass1 final : public AbstractClass {
protected:
void required_operation1() const override {
std::cout << "ConcreteClass1: required_operation1()\n";
}
void required_operation2() const override {
std::cout << "ConcreteClass1: required_operation2()\n";
}
};
/*
* Usually, concrete classes override only a fraction of base class' operations.
*/
class ConcreteClass2 final : public AbstractClass {
protected:
void required_operation1() const override {
std::cout << "ConcreteClass2: required_operation1()\n";
}
void required_operation2() const override {
std::cout << "ConcreteClass2: required_operation2()\n";
}
/*
* Overriding a hook to inject custom logic into the algorithm.
*/
void hook1() const override {
std::cout << "ConcreteClass2: hook1()\n";
}
};
/*
* The client code calls the template method to execute the algorithm. Client
* code does not have to know the concrete class of an object it works with, as
* long as it works with objects through the interface of their base class.
*/
void ClientCode(AbstractClass *class_) {
// ...
class_->template_method();
// ...
}
int main() {
std::cout << "Same client code can work with different subclasses:\n";
ConcreteClass1 *concrete_class1 = new ConcreteClass1;
ClientCode(static_cast<AbstractClass*>(concrete_class1));
std::cout << "\n";
std::cout << "Same client code can work with different subclasses:\n";
ConcreteClass2 *concrete_class2 = new ConcreteClass2;
ClientCode(static_cast<AbstractClass*>(concrete_class2));
delete concrete_class1;
delete concrete_class2;
return 0;
}
向上转换 (Upcasting):将派生类的指针或引用安全地转换为基类的指针或引用。这种转换是安全的,因为派生类总是包含基类的部分。
向下转型 (Downcasting):如果你想把基类转回子类,这是不安全的,必须使用 dynamic_cast() 进行显式转换。
在 C++ 中,派生类指针到基类指针的转换是隐式的且安全的,手动显式转换会增加代码冗余。
由于 ConcreteClass1 继承自 AbstractClass,编译器在底层认为 ConcreteClass1 的实例就是一个 AbstractClass 的实例。它包含了基类定义的所有成员。
使用 \n 代替 std::endl 可以获得更好的性能,减少不必要的缓冲区刷新。
Same client code can work with different subclasses:
AbstractClass: base_operation1()
ConcreteClass1: required_operation1()
AbstractClass: base_operation2()
ConcreteClass1: required_operation2()
AbstractClass: base_operation3()
Same client code can work with different subclasses:
AbstractClass: base_operation1()
ConcreteClass2: required_operation1()
AbstractClass: base_operation2()
ConcreteClass2: hook1()
ConcreteClass2: required_operation2()
AbstractClass: base_operation3()
请按任意键继续. . .
#include <iostream>
#include <memory>
#include <string>
/*
* The Abstract Class defines a template method that contains a skeleton of some
* algorithm, composed of calls to (usually) abstract primitive operations.
*
* Concrete subclasses should implement these operations, but leave the template
* method itself intact.
*/
class AbstractClass {
public:
/*
* Virtual destructor is crucial for polymorphic base classes to ensure
* derived objects are deleted correctly, preventing memory leaks.
*/
virtual ~AbstractClass() = default;
/*
* The template method defines the skeleton of an algorithm.
* Marked as 'final' to prevent subclasses from overriding the algorithm's structure.
*/
void template_method() const {
this->base_operation1();
this->required_operation1();
this->base_operation2();
this->hook1();
this->required_operation2();
this->base_operation3();
this->hook2();
}
protected:
/*
* These operations already have default implementations.
*/
void base_operation1() const {
std::cout << "AbstractClass: base_operation1()\n";
}
void base_operation2() const {
std::cout << "AbstractClass: base_operation2()\n";
}
void base_operation3() const {
std::cout << "AbstractClass: base_operation3()\n";
}
/*
* These operations have to be implemented in subclasses.
*/
virtual void required_operation1() const = 0;
virtual void required_operation2() const = 0;
/*
* These are "hooks." Subclasses may override them, but it's not mandatory
* since the hooks already have default (but empty) implementation. Hooks
* provide additional extension points in some crucial places of the algorithm.
*/
virtual void hook1() const {}
virtual void hook2() const {}
};
/*
* Concrete classes have to implement all abstract operations of the base class.
* They can also override some operations with a default implementation.
*/
class ConcreteClass1 final : public AbstractClass {
protected:
void required_operation1() const override {
std::cout << "ConcreteClass1: required_operation1()\n";
}
void required_operation2() const override {
std::cout << "ConcreteClass1: required_operation2()\n";
}
};
/*
* Usually, concrete classes override only a fraction of base class' operations.
*/
class ConcreteClass2 final : public AbstractClass {
protected:
void required_operation1() const override {
std::cout << "ConcreteClass2: required_operation1()\n";
}
void required_operation2() const override {
std::cout << "ConcreteClass2: required_operation2()\n";
}
/*
* Overriding a hook to inject custom logic into the algorithm.
*/
void hook1() const override {
std::cout << "ConcreteClass2: hook1()\n";
}
};
/*
* The client code calls the template method to execute the algorithm. Client
* code does not have to know the concrete class of an object it works with, as
* long as it works with objects through the interface of their base class.
*/
void ClientCode(AbstractClass &abstract_instance) {
abstract_instance.template_method();
}
int main() {
std::cout << "Same client code can work with different subclasses:\n";
std::unique_ptr<ConcreteClass1> concrete_class1 = std::make_unique<ConcreteClass1>();
ClientCode(*concrete_class1);
std::cout << "\n";
std::cout << "Same client code can work with different subclasses:\n";
std::unique_ptr<ConcreteClass2> concrete_class2 = std::make_unique<ConcreteClass2>();
ClientCode(*concrete_class2);
return 0;
}
Same client code can work with different subclasses:
AbstractClass: base_operation1()
ConcreteClass1: required_operation1()
AbstractClass: base_operation2()
ConcreteClass1: required_operation2()
AbstractClass: base_operation3()
Same client code can work with different subclasses:
AbstractClass: base_operation1()
ConcreteClass2: required_operation1()
AbstractClass: base_operation2()
ConcreteClass2: hook1()
ConcreteClass2: required_operation2()
AbstractClass: base_operation3()
请按任意键继续. . .
References
1\] Yongqiang Cheng (程永强),