好奇的探索者,理性的思考者,踏实的行动者。
Table of Contents:
《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,在本教程中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的“四人组”(Gang of Four,GoF)匿名著称。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
模式名称
问题
描述了该模式的应用环境,即何时使用该模式
解决方案
效果
描述了模式的应用效果以及使用该模式应该权衡的问题,即模式的优缺点。主要是对时间和空间的衡量,以及该模式对系统的灵活性、扩充性、可移植性的影响,也考虑其实现问题。
设计模式有两种分类方法,即根据模式的目的来分和根据模式的作用的范围来分。
根据目的来分
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
统一建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言.
UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。
在软件系统中,类不是孤立存在的,类与类之间存在各种关系。根据类与类之间的耦合度从弱到强排列
依赖关系
依赖(Dependency)关系是一种使用关系,在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
关联关系
关联(Association)关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。
在代码中通常将一个类的对象作为另一个类的成员变量来实现关联关系。
聚合关系
聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
4.组合关系
组合(Composition)关系也是关联关系的一种,也表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系,是 contains-a 关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
5.泛化关系
泛化(Generalization)关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系,是 is-a 的关系。
6.实现关系
实现(Realization)关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
软件实体应当对扩展开放,对修改关闭(Software entities should be open for extension,but closed for modification)
这里的软件实体包括以下几个部分:1.项目中划分出的模块 2.类与接口 3.方法
开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。
里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
依赖倒置原则的原始定义为:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指的是接口或者抽象类,而细节是指具体的实现类。
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
每个类尽量提供接口或抽象类,或者两者都具备。
变量的声明类型尽量是接口或者是抽象类。
任何类都不应该从具体类派生。
使用继承时尽量遵循里氏替换原则。
该原则提出对象不应该承担太多职责,否则类应该被拆分,如果一个对象承担了太多的职责,至少存在以下两个缺点:
一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。
接口隔离原则(Interface Segregation Principle,ISP)要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用
迪米特法则又叫作最少知识原则:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。
class Singleton
{
public:
static Singleton* GetInstance()
{
if ( inst == NULL )
inst = new Singleton();
return inst;
}
private:
Singleton(){};
static Singleton * inst;
class Singleton
{
private:
Singleton(){}
static Singleton * inst;
public:
static Singleton * GetInstance()
{
return inst;
}
};
Singleton::inst = new Singleton()
缺点是当新增产品的时候就要去修改工厂的类,这就违反了开放封闭原则,(类、模块、函数)可以扩展,但是不可以修改
#include<iostream>
using namespace std;
class Product
{
public:
virtual void show() = 0;
};
class Product_A : public Product
{
public:
void show()
{
cout << "Product_A" << endl;
}
};
class Product_B : public Product
{
public:
void show()
{
cout << "Product_B" << endl;
}
};
class Factory
{
public:
Product* Create(int i)
{
switch (i)
{
case 1:
return new Product_A;
break;
case 2:
return new Product_B;
break;
default:
break;
}
}
};
int main()
{
Factory *factory = new Factory();
factory->Create(1)->show();
factory->Create(2)->show();
system("pause");
return 0;
}
所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。打个比方现在有A、B两种产品,那么就开两个工厂。工厂A负责生产A产品,工厂B负责生产B种产品。
工厂方法模式的主要角色如下:
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
#include "stdafx.h"
#include<iostream>
using namespace std;
class Product
{
public:
virtual void show() = 0;
};
class Product_A : public Product
{
public:
void show()
{
cout << "Product_A" << endl;
}
};
class Product_B : public Product
{
public:
void show()
{
cout << "Product_B" << endl;
}
};
class Factory
{
public:
virtual Product* create() = 0;
};
class Factory_A : public Factory
{
public:
Product* create()
{
return new Product_A;
}
};
class Factory_B : public Factory
{
public:
Product* create()
{
return new Product_B;
}
};
int main()
{
Factory_A* productA = new Factory_A();
Factory_B* productB = new Factory_B();
productA->create()->show();
productB->create()->show();
system("pause");
return 0;
}
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式,其实俩都差不多。
#include <iostream>
using namespace std;
//定义抽象类
class product1
{
public:
virtual void show() = 0;
};
//定义具体类
class product_A1 :public product1
{
public:
void show(){ cout << "product A1" << endl; }
};
class product_B1 :public product1
{
public:
void show(){ cout << "product B1" << endl; }
};
//定义抽象类
class product2
{
public:
virtual void show() = 0;
};
//定义具体类
class product_A2 :public product2
{
public:
void show(){ cout << "product A2" << endl; }
};
class product_B2 :public product2
{
public:
void show(){ cout << "product B2" << endl; }
};
class Factory
{
public:
virtual product1 *creat1() = 0;
virtual product2 *creat2() = 0;
};
class FactoryA
{
public:
product1 *creat1(){ return new product_A1(); }
product2 *creat2(){ return new product_A2(); }
};
class FactoryB
{
public:
product1 *creat1(){ return new product_B1(); }
product2 *creat2(){ return new product_B2(); }
};
//===================
int main()
{
FactoryA *factoryA = new FactoryA();
factoryA->creat1()->show();
factoryA->creat2()->show();
FactoryB *factoryB = new FactoryB();
factoryB->creat1()->show();
factoryB->creat2()->show();
return 0;
}
在日常的开发中,经常遇到的场景是,这样的库对使用者不是很友好,如果多次在不同的地方用到这个库,则需要写好多次重复的模板代码:
int main()
{// 框架层逻辑
library lib(); // 应用层逻辑
application app();
lib.step1();
lib.step2();
if(app.step3())
{
lib.step4();
app.step5();
}
// xxx
}
如果把代码写成下面的样子,看看会不会更好?
class library
{public:
void step1();
void step2();
void step3();
// 提供给app实现
virtual void step3() = 0;
virtual void step5() = 0;
// 逻辑
void run()
{
step1();
step2();
if(step3())
{
step4();
step5();
}
}
};
class app : public library
{public:
void step3()
{// dosomething
}
void step5()
{// dosomething
}
};
int main()
{// 直接声明一个派生类对象
app myApp; // 使用基类引用来实现多态
library &lib = myApp;
lib.run(); }
对比一下,这样的好处是:
lib层面提供抽象的接口,step3(), step5(),继承的app类可以提供自己的实现。这样对于频繁的变化的app类,修改自己实现的step3,step5函数即可。
这样可能更加符合基本的设计原则,开放封闭原则 和 依赖倒置原则(依赖于抽象而不是依赖于具体)。
实际上,在现在的各种框架里面都可以看到这样的影子。lib层面在开发的层面上,已经提供了相关的逻辑的接口,app层面只需要继承这个接口,实现里面的逻辑即可。
策略模式使用的一个常见的场景:
如果你的代码里面有大量的if-else, switch-case, 那么可能是一个需要考虑策略模式了。
int main()
{
if (type == "中国")
{
// 使用中国的方式计算税务
}
else if (type == "美国")
{
// 使用美国的方式计算税务
}
else if (type == "xxx")
{
// 其他xxx
}
else
{
// 其他
}
}
这是一种看起来具有 “坏味道”的代码。
如果把代码写成下面的样子,看看会不会更好?
#include <iostream>
#include <string>
#include <memory>
// 抽象税务策略基类
class TaxStrategy {
public:
virtual double calculateTax(double income) = 0;
};
// 中国税务策略
class ChinaTaxStrategy : public TaxStrategy {
public:
double calculateTax(double income) override {
// 使用中国的税务计算方式
return income * 0.2; // 简化的计算方式
}
};
// 美国税务策略
class USATaxStrategy : public TaxStrategy {
public:
double calculateTax(double income) override {
// 使用美国的税务计算方式
return income * 0.3; // 简化的计算方式
}
};
// 客户端代码
int main() {
std::string country;
std::cout << "Enter your country (China/USA): ";
std::cin >> country;
std::unique_ptr<TaxStrategy> taxStrategy;
if (country == "China") {
taxStrategy = std::make_unique<ChinaTaxStrategy>();
} else if (country == "USA") {
taxStrategy = std::make_unique<USATaxStrategy>();
} else {
std::cout << "Invalid country" << std::endl;
return 1;
}
double income;
std::cout << "Enter your income: ";
std::cin >> income;
double tax = taxStrategy->calculateTax(income);
std::cout << "Tax to be paid: " << tax << std::endl;
return 0;
}
对比一下,这种模式的好处显而易见,通过使用多态的方式来控制“变化”。将变化控制在多态的函数里面。这是典型的开放封闭原则和依赖抽象的原则。
如果有一天一个人跳出来多,我还要新家几个类型,那对于这个模式来说,只需要新加一个class 继承一把,把逻辑写进行就可以。
观察者模式是一个非常经典的模式。在大量的软件中都会使用。这个就不举例子了,直接写这个模式的demo了。
class SUBJECT
{
vector<OBSERVER *> observer_vector;
public:
void add_ob();
void rm_ob();
void update()
{
for (xxx)
{
ob->notify();
}
}
};
class OBSERVER
{
public:
vitual void notify () = 0;
};
class OBSERVER_V1: public OBSERVER
{
public:
void notify ()
{
// do something
}
};
不同类型的observer可以继承基类observer,实现自己不同的notify函数。
Visitor 模式是一种设计模式,它允许你在不改变被访问元素的类的前提下,定义对这些元素进行操作的新操作。它将操作与元素的结构分离开来,使得添加新的操作更加容易,同时也支持在元素结构中添加新的元素类型。
其实就是访问者实现 visit()
方法,被访问者实现 accept()
方法,双方通过这个约定的接口来进行交互
在 C++ 中实现 Visitor 模式,通常包括两个关键的部分:元素类和 Visitor 类。这里我举个简单的例子来说明:
假设有一个图形类层次结构,包括不同类型的图形:圆形、矩形和三角形。我们希望对这些图形进行不同的操作,比如计算面积、计算周长等。我们可以使用 Visitor 模式来实现这个场景。
//===================被访问者===================
// Forward declaration of Visitor
class Visitor;
// Base class for all shapes
class Shape {
public:
virtual void accept(Visitor& visitor) = 0;
};
// Concrete Circle class
class Circle : public Shape {
public:
void accept(Visitor& visitor) override {
this);
visitor.visitCircle(*
}
// Other circle-specific methods and members
};
// Concrete Rectangle class
class Rectangle : public Shape {
public:
void accept(Visitor& visitor) override {
this);
visitor.visitRectangle(*
}
// Other rectangle-specific methods and members
};
//===================访问者===================
// Visitor interface
class Visitor {
public:
virtual void visitCircle(Circle& circle) = 0;
virtual void visitRectangle(Rectangle& rectangle) = 0;
};
// Concrete visitor implementing operations on shapes
class AreaCalculator : public Visitor {
public:
void visitCircle(Circle& circle) override {
// Calculate and display area of the circle
std::cout << "Calculating area of circle..." << std::endl;
// Perform area calculation logic here
}
void visitRectangle(Rectangle& rectangle) override {
// Calculate and display area of the rectangle
std::cout << "Calculating area of rectangle..." << std::endl;
// Perform area calculation logic here
}
};
class PerimeterCalculator : public Visitor {
public:
void visitCircle(Circle& circle) override {
// Calculate and display perimeter of the circle
std::cout << "Calculating perimeter of circle..." << std::endl;
// Perform perimeter calculation logic here
}
void visitRectangle(Rectangle& rectangle) override {
// Calculate and display perimeter of the rectangle
std::cout << "Calculating perimeter of rectangle..." << std::endl;
// Perform perimeter calculation logic here
}
};
//===========
int main() {
Circle circle;
Rectangle rectangle;
AreaCalculator areaCalc;
PerimeterCalculator perimeterCalc;
// Calculate area of circle
circle.accept(areaCalc); // Calculate area of rectangle
rectangle.accept(areaCalc);
// Calculate perimeter of circle
circle.accept(perimeterCalc); // Calculate perimeter of rectangle
rectangle.accept(perimeterCalc);
return 0;
}
在这个示例中,我们定义了图形类层次结构和一个 Visitor 类(ShapeVisitor
)。每个图形类(Circle
、Rectangle
、Triangle
)都实现了 accept()
方法,该方法接受一个 Visitor,并根据当前对象的类型调用 Visitor 的相应方法。ShapeVisitor
包含了不同图形类型的 visit()
方法,用于执行特定于每种图形的操作。在 main()
中,我们创建了不同类型的图形对象,并将一个具体的 Visitor(AreaVisitor
)传递给它们,以便进行特定的操作。
这个示例演示了如何使用 Visitor 模式来对不同类型的对象执行不同的操作,而不需要修改图形类的结构。
Visitor 模式主要用于在不改变数据结构的前提下定义对数据结构中各种元素的新操作。它使得可以在不改变元素类的情况下,向现有的类层次结构中添加新的操作,这些操作可以通过 Visitor 接口的实现类来执行。这种模式强调的是对数据结构的操作和行为的分离,使得能够轻松添加新的操作而不用修改元素类。
观察者模式用于定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在这种模式下,有两类角色:观察者(订阅者)和被观察者(发布者)。被观察者维护着观察者列表,并在状态变化时通知观察者。
享元模式的思路常见。就是当有可能重复创建对象的时候,对相同或相似对象的重用。
比如:
int create_obj(int key)
{
if (_map.find(key) == _map.end())
{
// 创建对象
// 插入map
}
else
{
// 直接返回对象
}
}
cpp文件
FLYWEIGHT_OBJ* FLYWEIGHT::build_obj(int key)
{
if (_map.find(key) == _map.end())
{
FLYWEIGHT_OBJ * obj = new FLYWEIGHT_OBJ();
_map[key] = obj;
return obj;
}
else
{
return _map[key];
}
}
int FLYWEIGHT::clear()
{
map<int, FLYWEIGHT_OBJ *>::iterator iter = _map.begin();
for (; iter != _map.end(); iter++)
{
FLYWEIGHT_OBJ * obj = iter->second;
delete obj;
}
return 0;
}
hpp文件
#ifndef __FLY_WEIGHT_H__
#define __FLY_WEIGHT_H__
#include <iostream>
#include <map>
using namespace std;
class FLYWEIGHT_OBJ
{
public:
FLYWEIGHT_OBJ():_data(0) {cout << "create obj" <<endl;}
private:
int _data;
};
class FLYWEIGHT
{
public:
FLYWEIGHT_OBJ * build_obj(int key);
int clear();
private:
std::map<int, FLYWEIGHT_OBJ *> _map;
};
#endif
proxy最常见的使用场景是,当开发的时候,看到一坨像shit一样的老代码,里面有一个函数func,需要调整or修改里面的部分逻辑。但是这个函数有无数个被引用的地方。直接改动func里面的逻辑,危险并且进一步使shit上再加shit。
因此,可以不去修改func内部的逻辑。可以重新写一个PROXY类继承原来的类,重新实现里面的逻辑。
class OLD_CLASS
{
int func()
{
// shit
}
};
class PROXY_SHIT : public OLD_CLASS
{
int proy_func()
{
pre_invoke();
// invoke shit
old_class->func();
post_invoke();
}
private:
OLD_CLASS * old_class;
}
使用的场景:
将一个类的接口转换为 用户希望的接口。可以是原本不兼容的接口 放到一起来使用。
举个栗子
// 开发的时候看到一个接口
int func(OLD_CLASS * a)
{
a->dosomething();
}
// 但是手上有的对象是B * b
int foo()
{
NEW_CLASS b;
func(&b); // complier error
}
如果让类型B转换为类型A, 适配器翻译的很准确,就是一个转换的插座。解决不同接口的类型的问题。
class OLD_CLASS
{
public:
virtual void dosomething() {cout<<"do something OLD_CLASS"<<endl; }
private:
int _data;
};
class NEW_CLASS
{
public:
void dosomething() {cout<<"do something NEW_CLASS"<<endl; }
private:
int _data;
};
class Adapter : public OLD_CLASS
{
public:
Adapter(NEW_CLASS * new_obj):_new_obj(new_obj) {}
void dosomething()
{
_new_obj->dosomething();
}
private:
NEW_CLASS * _new_obj;
};
int func(OLD_CLASS * old)
{
old->dosomething();
return 0;
}
int adapter_demo(NEW_CLASS * new_obj)
{
// 比如说直接调用因为参数不对而报错
// func(new)
Adapter adapter(new_obj);
adapter.dosomething();
return 0;
}
状态机模式可以被用在 软件设计中维护复杂的异步逻辑。
class STATE_MACHINE
{
int msg;
int state;
int next_stat;
};
基本的思路是维护一张函数表,在当前的state下,收到什么样的消息(或超时),执行什么样的逻辑,进入的下一个状态是什么。
责任链模式可以用于的场景是:
对于一个对象req,可以定义一系列的class(责任链)来处理它。感觉对于一个http的请求,或者是一个客户端上行来的请求,可以使用这种模式,来定义一条流水线(责任链)进行处理。
#include <iostream>
#include <string>
using namespace std;
enum class RequestType
{
REQ_HANDLER1,
REQ_HANDLER2,
REQ_HANDLER3
};
class Reqest
{
string description;
RequestType reqType;
public:
Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {}
RequestType getReqType() const { return reqType; }
const string& getDescription() const { return description; }
};
class ChainHandler{
ChainHandler *nextChain;
void sendReqestToNextHandler(const Reqest & req)
{
if (nextChain != nullptr)
nextChain->handle(req);
}
protected:
virtual bool canHandleRequest(const Reqest & req) = 0;
virtual void processRequest(const Reqest & req) = 0;
public:
ChainHandler() { nextChain = nullptr; }
void setNextChain(ChainHandler *next) { nextChain = next; }
void handle(const Reqest & req)
{
if (canHandleRequest(req))
processRequest(req);
else
sendReqestToNextHandler(req);
}
};
class Handler1 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override
{
return req.getReqType() == RequestType::REQ_HANDLER1;
}
void processRequest(const Reqest & req) override
{
cout << "Handler1 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler2 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override
{
return req.getReqType() == RequestType::REQ_HANDLER2;
}
void processRequest(const Reqest & req) override
{
cout << "Handler2 is handle reqest: " << req.getDescription() << endl;
}
};
class Handler3 : public ChainHandler{
protected:
bool canHandleRequest(const Reqest & req) override
{
return req.getReqType() == RequestType::REQ_HANDLER3;
}
void processRequest(const Reqest & req) override
{
cout << "Handler3 is handle reqest: " << req.getDescription() << endl;
}
};
int main(){
Handler1 h1;
Handler2 h2;
Handler3 h3;
h1.setNextChain(&h2);
h2.setNextChain(&h3);
Reqest req("process task ... ", RequestType::REQ_HANDLER3);
h1.handle(req);
return 0;
}