不要调用我,让我来调用你。
一个餐厅用餐的程序
假设我们把去餐厅点菜到上菜的过程写成一个程序——我们抽象出这五个主要的流程。
- 点菜
- 准备原料
- 清洗锅具
- 烹饪食物
- 装盘
在这五个流程中,清洗锅具和装盘是稳定的,但是点菜、准备原料和烹饪食物是变化的,它会随着菜品的不同而变化。
于是我们可以这样设计这个程序。我们定义基类 CookingProcess
,子类FriedSteak
和StewedBeef
继承自这个基类。基类中写好稳定的方法,把不稳定的方法定义成虚函数,留给子类实现。最后在基类中提供run
方法定义整个做饭算法的流程,此时调用稳定的非虚函数和变化的虚函数。在使用该类的时候只需要定义一个父类的多态指针,调用run
方法,run
再多态调用子类里对变化过程的实现。
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 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#include <iostream> using std::cout; using std::endl; class CookingProcess { public: virtual ~CookingProcess() {} void run() { orderDishes(); prepareIngredients(); cleanThePot(); cook(); serveDishes(); } protected: virtual void orderDishes() = 0; virtual void prepareIngredients() = 0; virtual void cook() = 0; void cleanThePot() { cout << "The pot has been cleaned." << endl; } void serveDishes() { cout << "Dishes are loaded on a plate." << endl; } }; class FriedSteak : public CookingProcess { protected: void orderDishes() { cout << "[name: Fried Steak]" << endl; } void prepareIngredients() { cout << "Prepare thyme and butter." << endl; } void cook() { cout << "Fry for three minutes." << endl; } }; class StewedBeef : public CookingProcess { protected: void orderDishes() { cout << "[name: Stewed Beef]" << endl; } void prepareIngredients() { cout << "Marinated beef for half an hour." << endl; } void cook() { cout << "When the fire boils, simmer for two hours." << endl; } }; int main() { CookingProcess *dishA = new FriedSteak(); CookingProcess *dishB = new StewedBeef(); dishA->run(); dishB->run(); delete dishA; delete dishB; return 0; } |
晚绑定
对应上面的例子,我们可以把CookingProcess
理解为是Library的开发,子类理解为是Application的开发。如果run
的过程交给Application开发者来写的话,形式上体现为子类调用父类,称为早绑定;如果交给Library的开发者来写的话,形式上父类调用子类,称为晚绑定。而晚绑定也是重构的关键技法之一。
回顾其他的语言中其实也存在很多晚绑定的例子,比如NodeJS的回调机制,再比如jQuery的$.ajax
函数,再比如说C语言的函数指针等等。它们的共同点是都调用了还没有实现的函数接口。
模式方法
GoF中对模式方法的定义是这样的:定义一个操作中的算法的骨架 (稳定),而将一些步骤延迟 (变化)到子类中。Template Method使得子类可以不改变 (复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤。
在这个例子中,稳定的算法骨架指的是CookingProcess::run
,变化指的是虚函数orderDishes
、prepareIngredients
和cook
。在这里我们一般把算法骨架中需要调用到的稳定的和不稳定的方法都定义成protected
,只将这个算法暴露出来,因为这些细小的步骤对于用户程序来说,单独调用是没有意义的。
所以我们在设计时,鉴定完稳定和变化的分解点之后,可以使用这种晚绑定的思路为程序提供更加灵活的结构,让父类处于“不要调用我,让我来调用你”的姿态,这样子类开发起来就更加轻松了。
说点什么