设计模式:组件协作 作者:wlai 发布:2024-04-02 更新:2025-02-09
现代软件专业分工之后的第一个结果是框架与应用程序的划分 ,组件协作模式通过晚期绑定 ,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
组件协作的典型模式:
(1)Template Method
(2)Strategy
(3)Observer/Event
一、模板方法 1.1 动机 在软件构建过程中,对于某一项任务,它常常有稳定的整体结构,但各个子步骤却有很多改变的需求 。或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现的需求?
1.2 定义 模板方法:模板方法定义一个操作中的算法的骨架(稳定),而将一些步骤延迟到子类中。模板方法使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤 。
下面是一个采用结构化设计思想的案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Library {public : void Step1 () { } void Step3 () { } void Step5 () { } };
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 class Application {public : bool Step2 () { } void Step4 () { } };int main () { Library lib () ; Application app () ; lib.Step1 (); if (app.Step2 ()) { lib.Step3 (); } for (int i = 0 ; i < 4 ; ++i) { app.Step4 (); } lib.Step5 (); return 0 ; }
对于上述结构化软件设计案例,可以用图来表示:
可以看到上述结构化软件设计案例中,是应用程序依赖了库函数,这是早绑定的。
应用模板方法,可以将上面的例子设计成以下的形式:
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 class Library {public : void Run () { Step1 (); if (Step2 ()) { Step3 (); } for (int i = 0 ; i < 4 ; ++i) { Step4 (); } Step5 (); } virtual ~Library () {}protected : void Step1 () { } void Step3 () { } void Step5 () { } virtual bool Step2 () = 0 ; virtual void Step4 () = 0 ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Application : public Library {protected : virtual bool Step2 () { } virtual void Step4 () { } };int main () { Library *pLib = new Application (); pLib->Run (); delete pLib; }
1.3 注意 (1)模板方法的结构如下所示,其中红色的部分是稳定的,蓝色是变化的部分;
(2)模板方法模式成立的前提是算法骨架(Run方法)是稳定的。如果算法骨架都不是稳定的,那么它就不适合用模板方法方法;
(3)如果所有步骤都是稳定不变的(极端情况下),那么就没有使用设计模式的必要了。设计模式的意义在于在变化(部分)和稳定(部分)之间寻找隔离点,从而管理变化;
(4)稳定和变化是相对的,没有绝对不变的东西,只有某部分代码相对其他代码更稳定一点;
1.4 总结 (1)模板方法模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性 )为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
(2)除了可以灵活地应对子步骤的变化外,“不要调用我,让我调用你”的反向控制结构是模板方法的典型应用。“不要调用我,让我调用你”,即你应用程序不要调用我程序库,让我来调用你。
(3)在具体实现方面,被模板方法调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设计为protected方法,不供外界调用。
二、策略模式 2.1 动机 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂。而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
2.2 定义 策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展、子类化) 。
如下所示,有同样功能的两段代码。
不使用策略模式:
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 enum TaxBase { CN_TAX, US_TAX, DE_TAX, FR_TAX };class SalesOrder { TaxBase tax;public : double CalculateTax () { if (tax == CN_TAX) { } else if (tax == US_TAX) { } else if (tax == DE_TAX) { } else if (tax == FR_TAX) { } } };
不使用策略模式存在的问题:
(1)假如要新增FR_TAX
类型的税种计算,那么就会产生更改。这违背了开闭原则
使用策略模式:
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 class TaxStrategy {public : virtual double Calculate (const Context& context) = 0 ; virtual ~TaxStrategy () {} };class CNTax : public TaxStrategy {public : virtual double Calculate (const Context& context) { } };class USTax : public TaxStrategy {public : virtual double Calculate (const Context& context) { } };class DETax : public TaxStrategy {public : virtual double Calculate (const Context& context) { } };class FRTax : public TaxStrategy {public : virtual double Calculate (const Context& context) { } };class SalesOrder {private : TaxStrategy* strategy;public : SalesOrder (StrategyFactory* strategyFactory) { this ->strategy = strategyFactory->NewStrategy (); } ~SalesOrder () { delete this ->strategy; } public double CalculateTax () { Context context () ; double val = strategy->Calculate (context); } };
使用策略模式的好处:
(1)假如要新增FR_TAX
类型的税种计算,那么只需要进行扩展而无需进行更改;
(2)上述代码的可复用性得到了提升;
2.3 总结 (1)策略模式的结构图如下,其中红色是稳定部分,蓝色为变化部分;
(2)Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时 方便地根据需要在各个算法之间进行切换;
(3)Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常需要Strategy模式;
(4)如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销;
三、观察者模式 3.1 动机 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使得软件不能很好地抵御变化。
使用面向对象的技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
3.2 模式定义 观察者模式:定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象(Observer)都得到通知并自动更新 。
假如我们要做一个文件分割器的小软件,它的初始源码可以是以下的形式。此时,还不涉及到观察者模式。
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 class FileSplitter { string filePath; int fileNumber;public : FileSplitter (const string &filePath, int num) : filePath (filePath), fileNumber (num) {} void split (void ) { for (int i = 0 ; i < fileNumber; i++) { } } };class MainForm : public Form { TextBox *txtFilePath; TextBox *txtFileNumber;public : void Button1_Click () { string filePath = txtFilePath->getText (); int number = atoi (txtFileNumber->getText ().c_str ()); FileSplitter splitter (filePath, number) ; splitter.split (); } };
假如要添加一个进度条的功能,最简单的方法是如下的写法。
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 class FileSplitter { string filePath; int fileNumber; ProgressBar *progressBar; public : FileSplitter (const string &filePath, int num, ProgressBar *progress) : filePath (filePath), fileNumber (num), progressBar (progress) {} void split (void ) { for (int i = 0 ; i < fileNumber; i++) { if (progressBar != nullptr ) { progressBar->setValue ((i + 1 ) / fileNumber); } } } };class MainForm : public Form { TextBox *txtFilePath; TextBox *txtFileNumber; ProgressBar *progress; public : void Button1_Click () { string filePath = txtFilePath->getText (); int number = atoi (txtFileNumber->getText ().c_str ()); FileSplitter splitter (filePath, number, progress) ; splitter.split (); } };
上述代码存在什么问题?
它违反了依赖倒置原则,即抽象不应该依赖于实现细节,实现细节应该依赖于抽象。FileSplitter
中新增的progressBar
就是实现细节。一旦提示的形式变了(这是很容易发生的),不再是进度条了,那么上述代码就需要跟着变动。
仔细分析一下,progressBar
在代码起到的作用是通知 ,如何用一种更加抽象的机制来起到通知的功能呢?下面的代码就不再违反依赖倒置原则了,原因在于FilterSplitter
由原来的依赖具体ProgressBar
类变成了依赖抽象的通知机制IProgress
。
此时的代码是只有一个观察者的观察者模式,观察者对象是MainForm
,目标对象是FileSplitter
。
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 class IProgress {public : virtual void DoProgress (float value) = 0 ; virtual ~IProgress () {} };class FileSplitter { string filePath; int fileNumber; IProgress *progress; public : FileSplitter (const string &filePath, int num, IProgress *progress) : filePath (filePath), fileNumber (num), progress (progress) {} void split (void ) { for (int i = 0 ; i < fileNumber; i++) { if (progress != nullptr ) { float value = (i + 1 ) / fileNumber; progress->DoProgress (value); } } } };class MainForm : public Form, public IProgress { TextBox *txtFilePath; TextBox *txtFileNumber; ProgressBar *progress; public : void Button1_Click () { string filePath = txtFilePath->getText (); int number = atoi (txtFileNumber->getText ().c_str ()); FileSplitter splitter (filePath, number, this ) ; splitter.split (); } virtual void DoProgress (float value) { progress->setValue (value); } };
但是,如果需要有多个观察者对象又该怎么办呢?上述的代码显然就不好支持多个观察者对象了。此时就需要进行多观察者改造了,具体代码如下。至此,终于得到了完整的观察者模式代码。
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 75 76 77 78 79 80 81 82 83 84 85 86 87 class IProgress {public : virtual void DoProgress (float value) = 0 ; virtual ~IProgress () {} };class FileSplitter { string filePath; int fileNumber; list<IProgress*> progress; public : FileSplitter (const string &filePath, int num) : filePath (filePath), fileNumber (num) {} void AddIProgress (IProgress *i) { progress.add (i); } void RemoveIProgress (IProgress *i) { progress.remove (i); } void split (void ) { for (int i = 0 ; i < fileNumber; i++) { float value = (i + 1 ) / fileNumber; OnProgress (value); } }protected : void OnProgress (float value) { list<IProgress*>::iterator iter = progress.begin (); while (iter != progress.end ()) { (*iter)->DoProgress (value); } } };class MainForm : public Form, public IProgress { TextBox *txtFilePath; TextBox *txtFileNumber; ProgressBar *progressBar; public : void Button1_Click () { string filePath = txtFilePath->getText (); int number = atoi (txtFileNumber->getText ().c_str ()); ConsoleNotifier cn; FileSplitter splitter (filePath, number) ; splitter.AddIProgress (this ); splitter.AddIProgress (&cn); splitter.split (); splitter.RemoveIProgress (&cn); splitter.RemoveIProgress (this ); } virtual void DoProgress (float value) { progressBar->setValue (value); } };class ConsoleNotifier : public IProgress {public : virtual void DoProgress (float value) { cout << "." ; } };
3.3 要点总结 (1)观察者模式的结构如下所示,红色部分为稳定部分,蓝色部分为不稳定部分。
Subject为目标对象,其中的Attach表示往其中新增观察者,Detach表示移除观察者,Notify表示对所有的观察者进行通知。Observer为观察者对象。
(2)使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
(3)目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
(4)观察者自己决定是否需要订阅通知,目标对象对此一无所知。
(5)Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
3.4 实践 手写观察者模式
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 #ifndef XXX_SEND_EVENT_H_ #define XXX_SEND_EVENT_H_ typedef struct _xxx_event { int value; } xxx_event;typedef void (*xxx_send_func) (xxx_event *event) ;typedef enum _xxx_obsr_id { OBSR_ID_1, OBSR_ID_2, OBSR_ID_3, OBSR_ID_BUTT } xxx_obsr_id;int xxx_register_observer (xxx_obsr_id id, xxx_send_func func) ;int xxx_unregister_observer (xxx_obsr_id id) ;void xxx_send_event (void ) ;#endif
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 #include <stdio.h> #include <assert.h> #include "xxx_send_event.h" #define ATTACH_TRUE (1) #define ATTACH_FALSE (0) typedef struct _xxx_observer { int is_attach; xxx_send_func func; } xxx_observer;static xxx_observer g_obsr_table[OBSR_ID_BUTT];int xxx_register_observer (xxx_obsr_id id, xxx_send_func func) { if (id < 0 || id >= OBSR_ID_BUTT || func == NULL ) { fprintf (stderr , "[ERROR] register observer failed due to invalid param." ); return -1 ; } if (g_obsr_table[id].is_attach == ATTACH_TRUE) { fprintf (stderr , "[WARN] observer(id:%d) registered before, re-register now." , id); } g_obsr_table[id].is_attach = ATTACH_TRUE; g_obsr_table[id].func = func; return 0 ; }int xxx_unregister_observer (xxx_obsr_id id) { if (id < 0 || id >= OBSR_ID_BUTT) { fprintf (stderr , "[ERROR] unregister observer(id: %d) failed due to invalid param" , id); return -1 ; } if (g_obsr_table[id].is_attach == ATTACH_FALSE) { fprintf (stderr , "[WARN] observer(id:%d) unregistered before, re-unregister now." , id); } g_obsr_table[id].is_attach = ATTACH_FALSE; g_obsr_table[id].func = NULL ; return 0 ; }void xxx_send_event (void ) { xxx_event event; xxx_send_func func; for (int i = 0 ; i < OBSR_ID_BUTT; i++) { if (g_obsr_table[i].is_attach == ATTACH_TRUE) { event.value = i; func = g_obsr_table[i].func; assert(func != NULL ); func(&event); } } return ; }
注意:
(1)所有xxx的观察者都必须在xxx_obsr_id
中注册其ID;
(2)在xxx的观察者注册发送函数时需要提供ID号和对应的函数指针;
(3)目标xxx通过xxx_send_event
接口按照ID号的注册顺序依次进行通知;
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 #include <stdio.h> #include <stddef.h> #include <assert.h> #include "xxx_send_event.h" void func1 (xxx_event *event) { assert(event != NULL ); printf ("Observer 1 received event(value:%d).\n" , event->value); }void func2 (xxx_event *event) { assert(event != NULL ); printf ("Observer 2 received event(value:%d).\n" , event->value); }void func3 (xxx_event *event) { assert(event != NULL ); printf ("Observer 3 received event(value:%d).\n" , event->value); }int main () { xxx_register_observer(OBSR_ID_1, func1); xxx_register_observer(OBSR_ID_2, func2); xxx_register_observer(OBSR_ID_3, func3); xxx_send_event(); printf ("------\n" ); xxx_unregister_observer(OBSR_ID_3); xxx_send_event(); printf ("------\n" ); xxx_unregister_observer(OBSR_ID_1); xxx_send_event(); printf ("------\n" ); xxx_register_observer(OBSR_ID_3, func3); xxx_send_event(); return 0 ; }
测试结果如下:
1 2 3 4 5 6 7 8 9 10 11 Observer 1 received event(value:0). Observer 2 received event(value:1).Observer 3 received event(value:2). ------ Observer 1 received event(value:0).Observer 2 received event(value:1). ------ Observer 2 received event(value:1). ------ Observer 2 received event(value:1). Observer 3 received event(value:2).