
科普文,给大家介绍观察者模式的使用场合及其优缺点。
模式动机 6 W, Z1 v( _, B4 f# N 观察者模式是比较常用的设计模式之一,尤其是系统里面涉及到多个复杂子系统时,经常会使用到。 它就像系统里面某个子模块的跑腿,一旦该子模块发生变化,它就要为这个子模块通知其他的子模块。 一个经典的例子就是我们操作系统所使用到的GUI界面,当我们在GUI系统里面使用各种应用程序时,只需要用鼠标轻轻点击软件右上方的全屏/非全屏,软件里面全部的组件就会进行相应的缩放,这里面使用到的就是观察者模式。 观察者模式定义:
场景案例 在单片机开发里面,串口通信是很重要的通信手段。在业务代码里面,有很多子模块都关注着串口通信的数据。假设在串口数据来临的时候,我们需要去通知各个子模块。 伪代码实现: //串口中断/ V* g- p" O9 ^" r; {6 N. vurat_isr() {: j( k7 ]& U! l: \2 t2 u1 ^, @ ... //通过设置全局变量来通知子模块一 notify_module1 = 1; //通过设置全局变量来通知子模块二 notify_module2 = 1; //通过设置全局变量来通知子模块三 notify_module3 = 1; ..., Z1 @, \! W& ?. J- W( R/ \% b" c }' e$ T0 R6 n( M( E' W) l* Z 9 F/ z- F, @" g8 S1 f5 D ... //主函数,创建多个线程来处理不同任务4 ^# E4 B7 C% ^ int main()4 D+ o- Y- J' E { ... //线程1(子模块一) create_thread1(); //线程2(子模块二) create_thread2();5 Z0 z0 ?6 P5 P& A //线程3(子模块三) create_thread3(); ...' T8 K0 N# Q- L3 T1 V! D. h, t, x2 i 5 a# u4 @0 T0 b; ]$ R) b } 在上面的代码实现中,串口数据发生更新时,通过给各个全局变量置1来通知各个子模块。等到各个子模块得到运行机会后,判断并更新串口通信数据。 在这个实现方案中,串口数据通过全局变量来通知子模块的方式非常死板,一旦需要通知的子模块发生变化,必须要改动串口中断部分代码。
改进方案 在多个子系统同时监视某一个子系统时,应该添加一个观察者模块,来解开通信引起的子系统耦合。 伪代码实现: //定义观察对象的数量7 J4 x+ k" l7 \$ U: Q" n( a0 y#define num 3% y5 H! l, d: K N* H, \ - v2 C8 j' J1 ~. g9 S6 o //定义观察对象3 r3 ]! L' o; K0 k; i1 u2 m: D) d typedef struct object: U# \1 p z. \/ Z { //定义观察对象的通知接口 void (*update)(); }Object;; L& u( E- ^6 k8 q4 a - S3 j/ W2 o( G //定义观察者模块 typedef struct observer { Object* objectList[num]; }Observer; //定义一个观察者模块 Observer aobserver; //主函数,创建多个线程来处理不同任务 int main()* D5 l. V+ I1 W) R {: C- B* T/ N: L4 l- W //初始化各个观察对象 aobserver.objectList[0]->update = Update_module1;% c* a: u" {4 G# }! h aobserver.objectList[1]->update = Update_module2; aobserver.objectList[2]->update = UpdateUpdate_module3;9 m& C8 W+ w, m( v3 v ... //线程1(子模块一) create_thread1();$ D1 q5 F6 ?$ J1 O+ {0 D: L //线程2(子模块二) create_thread2();$ c. S- S( Q$ G' z, } //线程3(子模块三)$ u4 K& J1 H: f( ~/ s0 R H F create_thread3(); ...$ P) [, m* f' N6 M3 V } //串口中断 urat_isr()) l# h: M( q( F5 K. @! _ {; u/ {6 Z, V$ v5 _3 f ...' ?4 G1 k& }- X1 O7 U* C for(i = 0; i < num; i++)/ j7 C* }+ R; e! ]* A0 ~! v% ` aobserver.objectList->update(); ... } 2 _8 Z3 a* g4 ^5 K& V, x* o4 O' S 各个子模块的通知接口,可以像这样子来实现: //module1通知接口. [8 r6 R4 _" I# N; u7 \void Update_module1() { //通过设置全局变量来通知子模块一0 {0 T% |' c% \! |9 W notify_module1 = 1; }7 y3 \1 C4 {7 {* M/ n% I% y+ x //module2通知接口' z$ v* A1 v! i void Update_module2() {1 K* T# u4 ~) d- k" i/ T6 @9 b //通过设置全局变量来通知子模块一8 P5 @7 w8 d' g7 F2 I& U notify_module2 = 1;- e- _# c( L0 H; | }* R; ]2 e/ y; [: v0 y% R . P7 L' x, N6 E- j //module3通知接口& h+ p3 y- a0 L a' h* g void Update_module3() {& [6 S7 w( V* U6 k( d+ Z c //通过设置全局变量来通知子模块一 notify_module3 = 1;0 o0 h% I" Q/ x" l } 总结$ D, f7 Q' n, n * q6 _- v' I. { " I2 Z+ h$ w; k% X+ K4 W' \ 这就是c语言中的观察者模式,它可以动态地增加、减少观察对象,解除子模块间的直接耦合,可以很好地预防程序需求发生变化。 但是在实际使用过程中,需要考虑一下开发效率和运行效率问题:
|