
科普文,给大家介绍观察者模式的使用场合及其优缺点。
模式动机 + z. A& Q) A2 h/ J% Y1 } 观察者模式是比较常用的设计模式之一,尤其是系统里面涉及到多个复杂子系统时,经常会使用到。 它就像系统里面某个子模块的跑腿,一旦该子模块发生变化,它就要为这个子模块通知其他的子模块。 一个经典的例子就是我们操作系统所使用到的GUI界面,当我们在GUI系统里面使用各种应用程序时,只需要用鼠标轻轻点击软件右上方的全屏/非全屏,软件里面全部的组件就会进行相应的缩放,这里面使用到的就是观察者模式。 观察者模式定义:
场景案例) M. Q$ a+ p* @# p# y6 ^ 在单片机开发里面,串口通信是很重要的通信手段。在业务代码里面,有很多子模块都关注着串口通信的数据。假设在串口数据来临的时候,我们需要去通知各个子模块。 伪代码实现: //串口中断8 N+ l" {; g% q( Qurat_isr(). g/ T! E9 l+ i" L% _9 x { ... //通过设置全局变量来通知子模块一4 S' V8 I, x, U; P& T notify_module1 = 1;$ h( s( \$ x4 I& I& P! ~+ C //通过设置全局变量来通知子模块二 notify_module2 = 1; //通过设置全局变量来通知子模块三 notify_module3 = 1;8 N- }: ?: G; K7 z/ G7 H ..." ^0 F1 b& Y) W }6 P. l9 o2 h% ]* @) B2 m& A3 s7 ~ .... M1 X ^3 Z. G' [# q: H //主函数,创建多个线程来处理不同任务0 @ T) [8 W# u# }' @7 A int main() {' J( e( U$ n0 g6 a& _ ... //线程1(子模块一)9 ^* n# g$ z' r5 I, m5 | create_thread1(); U& ?7 f* q5 w; C3 z //线程2(子模块二)$ G2 h; E0 ?7 v9 Y$ \ create_thread2(); //线程3(子模块三)+ f$ F+ |3 X e% n$ w: ^ create_thread3(); ... } 在上面的代码实现中,串口数据发生更新时,通过给各个全局变量置1来通知各个子模块。等到各个子模块得到运行机会后,判断并更新串口通信数据。 在这个实现方案中,串口数据通过全局变量来通知子模块的方式非常死板,一旦需要通知的子模块发生变化,必须要改动串口中断部分代码。
改进方案 Z6 S6 x) b2 X: m; q! W 6 @4 u# y- C0 T$ l( l# l& T: K 在多个子系统同时监视某一个子系统时,应该添加一个观察者模块,来解开通信引起的子系统耦合。 伪代码实现: //定义观察对象的数量#define num 3 ) f+ x- G$ ^+ Z) E$ ?) S) G; e" C6 w //定义观察对象8 O6 u: j- s0 J. [- o G typedef struct object' Z0 C# s; K! @* h' ?) c% O {! \' h9 c; e# Y. M //定义观察对象的通知接口: L0 d6 M" E) J- p& |+ V' W void (*update)();& ]8 f- y+ `3 s4 m8 f }Object;0 A# \' q" S9 d. X //定义观察者模块# o& `; g! T& v! A I* @3 f2 s typedef struct observer {2 V* A2 J- R. p/ V+ {$ Y Object* objectList[num]; }Observer;$ }" P S5 t" A //定义一个观察者模块 Observer aobserver;& J7 V6 e' v" D$ x //主函数,创建多个线程来处理不同任务' m' N" t, A: J# f int main()0 |* H, y3 Q9 O { //初始化各个观察对象 aobserver.objectList[0]->update = Update_module1; aobserver.objectList[1]->update = Update_module2;: v- V+ `0 X/ I4 v' V aobserver.objectList[2]->update = UpdateUpdate_module3; ... //线程1(子模块一)- I$ s, D& G' [ create_thread1(); //线程2(子模块二) create_thread2();$ h0 {7 N& ^2 b. e5 k. Z5 A //线程3(子模块三)1 ^1 E- h w9 D2 u8 |/ c create_thread3();$ P+ F F N; O& j" Z. w ... }) j; I. {, Z! O- _( K //串口中断 urat_isr()8 W$ ?8 z% f) n6 Q9 M4 Q {2 `: Z: S4 ~- Z1 D! u* l J ... for(i = 0; i < num; i++) aobserver.objectList->update(); ...4 l$ ]4 b- C( w. H( p% X2 {3 u } 各个子模块的通知接口,可以像这样子来实现: //module1通知接口void Update_module1()' V& r, B0 A2 \. Q$ P {8 `* y; L6 D5 i( m% T9 K //通过设置全局变量来通知子模块一 notify_module1 = 1; } //module2通知接口- A. {9 P+ K" w8 W6 N) ? void Update_module2() { //通过设置全局变量来通知子模块一& c4 L/ |+ h/ z; D notify_module2 = 1;/ S% H: `" y* s9 L1 S$ L @" T } 1 w& m# M4 {' A% _5 M //module3通知接口- ?. J) h3 X5 A9 _; c& ]0 v% U8 H. P void Update_module3() { //通过设置全局变量来通知子模块一: `6 Y, b, T# G0 c/ r H J: K7 L. q notify_module3 = 1;2 {$ j6 W- o$ R }% q) b. t+ l9 ~ k 总结% X+ O- K7 s0 B" H5 C 这就是c语言中的观察者模式,它可以动态地增加、减少观察对象,解除子模块间的直接耦合,可以很好地预防程序需求发生变化。 但是在实际使用过程中,需要考虑一下开发效率和运行效率问题:
|