设计模式读书笔记5--模板方法模式
在现实生活中,很多事情都需要经过几个步骤才能完成,例如请客吃饭,无论吃什么,一般都包含:点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单=>吃东西=>买单。在这3个步骤中,点单和买单大同小异,最大的区别在于第2步-吃什么?吃面条和吃满汉全席可大不相同。
在软件开发中,有时候也会遇到类似的情况,某个方法的实现需要多个步骤(类似于“请客”),其中有些步骤是固定的,而有些步骤则存在可变性。为了提高代码复用性和系统灵活性,可以使用一种称之为模板方法模式的设计模式来对这类情况进行设计。
模板方法模式(Template Method) 学习难度:★★☆☆☆ 使用频率:★★★☆☆
1 需求导入
公司需要一个星期搞定10万的车模,需要完成分析,模板,而是,还要考虑扩展性,稳定性,悍马车有两个车型H1和H2,按照需求,只需要悍马模型,先写个抽象类,然后两个不同型号的模型实现,通过简单的继承实现业务需求。
2 模板方法模式的简介
2.1 定义
模板方法可以算是最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系,其定义如下:
模板方法(Template Method)模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的特定步骤。模板方法是一种行为型模式。
2.2 模板方法模式结构
- AbstractClass(抽象类):在抽象类中定义了一系列基本操作(Primitive Operations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重新定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架。
- ConcreteClass(具体子类):抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
3 代码实现
3.1 抽象实现
typedef struct _HummerModel HummerModel;
struct _HummerModel
{
void (*start)(void);
void (*stop)(void);
void (*alarm)(void);
void (*engineBoom)(void);
void (*run)(HummerModel *pHummerModel);
BOOLEAN (*isAlarm)(void);
};
在抽象中,我们定义了悍马模型必须有的特质,能够发动,停止,喇叭会响,引擎可以轰鸣,而且还可以停止。
3.2 具体子类
但是每个子类实现是不同,对于H1型号代码清单,对于与H2不一样的是,H1是不会鸣笛的,其结构对象定义为:
typedef struct _HumerH1Model HumerH1Model;
static BOOLEAN s_HumerH1ModelAlarmFlag = FALSE;
struct _HumerH1Model
{
HummerModel *phummerModel;
void (*setAlarm)(BOOLEAN isAlarm);
void (*DeleteHumerH1Model)(HumerH1Model *pHumerH1Model);
};
其代码实现为
void HummerH1SetAlarm(BOOLEAN isAlarm)
{
s_HumerH1ModelAlarmFlag = isAlarm;
}
BOOLEAN GetHumerH1Alarm(void)
{
return s_HumerH1ModelAlarmFlag;
}
void HumerH1Start(void)
{
printf("HumerH1Model start ....\n");
}
void HumerH1Alarm(void)
{
printf("HumerH1Model alarm ...\n");
}
void HumerH1EngineBoom(void)
{
printf("HumerH1Mode engineBoom ...\n");
}
void HumerH1Stop(void)
{
printf("HumerH1Mode Stop ...\n");
}
void DeleteHumerH1Model(HumerH1Model *pHumerH1Model)
{
if(pHumerH1Model->phummerModel)
{
free(pHumerH1Model->phummerModel);
}
if(pHumerH1Model)
{
free(pHumerH1Model);
pHumerH1Model = NULL;
}
}
HumerH1Model *CreateHumerH1Model(void)
{
HumerH1Model *pHumerH1Model = (HumerH1Model *)malloc(sizeof(HumerH1Model));
if(!pHumerH1Model)
{
return NULL;
}
memset(pHumerH1Model, 0, sizeof(HumerH1Model));
pHumerH1Model->phummerModel = CreateHummerModel();
if(!pHumerH1Model->phummerModel)
{
return NULL;
}
pHumerH1Model->setAlarm = HumerH1Alarm;
pHumerH1Model->DeleteHumerH1Model = DeleteHumerH1Model;
pHumerH1Model->phummerModel->start = HumerH1Start;
pHumerH1Model->phummerModel->stop = HumerH1Stop;
pHumerH1Model->phummerModel->alarm = HumerH1Alarm;
pHumerH1Model->phummerModel->engineBoom = HumerH1EngineBoom;
pHumerH1Model->phummerModel->isAlarm = GetHumerH1Alarm;
return pHumerH1Model;
}
一个模型定义成功了,总要拿给客户检测,怎么检测,“是骡子是马,拉出去溜溜“,这是唯一的检测方法,通过run方法,把模型所有的功能都测试到。而对于H1和H2,其检测的方式是一样的,差别在与,H1是不要鸣笛,H2是需要鸣笛,那么这个run方法就应该在抽象类,不应该在实现类上,抽象是所有子类共有的封装。抽象类中,HummerModel中的run方法,其源码为
void run(HummerModel *pHummerModel)
{
pHummerModel->start();
pHummerModel->engineBoom();
if(pHummerModel->isAlarm())
{
pHummerModel->alarm();
}
pHummerModel->stop();
}
HummerModel *CreateHummerModel(void)
{
HummerModel *pHummerModel = (HummerModel *)malloc(sizeof(HummerModel));
if(!pHummerModel)
{
return NULL;
}
memset(pHummerModel, 0, sizeof(HummerModel));
pHummerModel->run = run;
return pHummerModel;
}
3.3 场景实现
场景实现的任务就是把生产的模型展现给客户,源码清单为:
void main()
{
int input = 0;
HumerH1Model *pHumerH1Model = NULL;
pHumerH1Model = CreateHumerH1Model();
if(!pHumerH1Model)
{
return;
}
printf("-----------H1型号悍马------------\n");
printf("是否需要喇叭响? 0-不需要 1-需要\n");
scanf("%d\n", &input);
if(1 == input)
{
pHumerH1Model->setAlarm(TRUE);
}
else
{
pHumerH1Model->setAlarm(FALSE);
}
pHumerH1Model->phummerModel->run(pHumerH1Model->phummerModel);
pHumerH1Model->DeleteHumerH1Model(pHumerH1Model);
}
4 模板方法模式的应用
4.1 模板方法模式的优点
- 封装不变部分,扩展可变部分,把认为不变的部分算法封装到父类实现,而可变部分则通过继承来扩展。在本例中的悍马模型中,是不是很容易扩展?例如要增加一个悍马H3模型,很容易,增加一个子类,实现父类的基本方法就可以了。
2 提供公共部分代码,便于维护
3 行为由父类控制,基本方法由子类实现,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
4.2 模板方法模式的缺点
- 按照设计习惯,抽象类负责声明最抽象,最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响父类的结果,导致代码阅读难度。
- 需要为每一个基本方法的不同实现一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也会更加抽象。
4.3 模板方法模式应用场景
- 多个子类有公用的方法,并且逻辑基本相同
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能由各个子类实现
- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽象到父类中,然后通过钩子函数约束其行为。
5 参考资料
秦晓波, 《设计模式之禅》
刘伟,《设计模式的艺术—软件开发人员内功修炼之道》