你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

c语言中的观察者模式

[复制链接]
gaosmile 发布时间:2020-5-29 12:15

科普文,给大家介绍观察者模式的使用场合及其优缺点。


# G. l$ |" ~5 e% w  ]' W

模式动机

+ z. A& Q) A2 h/ J% Y1 }

观察者模式是比较常用的设计模式之一,尤其是系统里面涉及到多个复杂子系统时,经常会使用到。
, Q7 I9 F& U( H+ L% Z' P8 n: C& _

它就像系统里面某个子模块的跑腿,一旦该子模块发生变化,它就要为这个子模块通知其他的子模块。

一个经典的例子就是我们操作系统所使用到的GUI界面,当我们在GUI系统里面使用各种应用程序时,只需要用鼠标轻轻点击软件右上方的全屏/非全屏,软件里面全部的组件就会进行相应的缩放,这里面使用到的就是观察者模式。

观察者模式定义:

  • 定义对象之间多对一的依赖关系,保证当被依赖的对象发生变化时,所有的依赖者会被自动更新。
  • 当一个对象需要通知另外一些对象,而你无法预知哪些对象将被通知时,通过观察者模式,克制减少对象的偶合关系。
    ! Q. D" O" r% B8 ~2 S6 A. u- r* }

+ {) Z. T: L7 e$ v0 p: a
场景案例) M. Q$ a+ p* @# p# y6 ^

" |/ D9 B! I/ A( k) V* T

在单片机开发里面,串口通信是很重要的通信手段。在业务代码里面,有很多子模块都关注着串口通信的数据。假设在串口数据来临的时候,我们需要去通知各个子模块。

伪代码实现:

//串口中断8 N+ l" {; g% q( Q
urat_isr(). g/ T! E9 l+ i" L% _9 x
{
  q% u: k/ d0 T* I) f. N. g, C...
$ R- u% ~8 K/ {1 j/ _+ R  //通过设置全局变量来通知子模块一4 S' V8 I, x, U; P& T
  notify_module1 = 1;$ h( s( \$ x4 I& I& P! ~+ C
  //通过设置全局变量来通知子模块二
- g$ n- t/ o8 q: U. [; S  notify_module2 = 1;
- x% Z; }. R+ t/ {- b  //通过设置全局变量来通知子模块三
% Q% V2 }" }/ A  notify_module3 = 1;8 N- }: ?: G; K7 z/ G7 H
..." ^0 F1 b& Y) W
}6 P. l9 o2 h% ]* @) B2 m& A3 s7 ~

4 X. Z( b, o4 l0 m5 D% k5 M2 l: m.... M1 X  ^3 Z. G' [# q: H
//主函数,创建多个线程来处理不同任务0 @  T) [8 W# u# }' @7 A
int main()
) t# ]3 d. q2 D" l4 A{' J( e( U$ n0 g6 a& _
...
% R: e" j& A2 @' ?  R//线程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();
5 d0 H: u8 @7 p, F- o! E//线程3(子模块三)+ f$ F+ |3 X  e% n$ w: ^
create_thread3();
$ k+ a6 U, E- r...
& ?/ f  B9 E1 J
, G" E* `8 c2 C}

) _6 Z" W% g/ H! M" H, E. l

在上面的代码实现中,串口数据发生更新时,通过给各个全局变量置1来通知各个子模块。等到各个子模块得到运行机会后,判断并更新串口通信数据。

在这个实现方案中,串口数据通过全局变量来通知子模块的方式非常死板,一旦需要通知的子模块发生变化,必须要改动串口中断部分代码。


2 }& w% I& j; e  g% {+ H* }& R. l

改进方案  Z6 S6 x) b2 X: m; q! W
6 @4 u# y- C0 T$ l( l# l& T: K

在多个子系统同时监视某一个子系统时,应该添加一个观察者模块,来解开通信引起的子系统耦合。

伪代码实现:

//定义观察对象的数量
, C! ^, S- F/ K1 h) N#define num 3
6 _8 n2 E" U" a9 V/ \- F# y, N* ]) 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

  ^' e2 o( R1 V3 u& Q7 A//定义观察者模块# o& `; g! T& v! A  I* @3 f2 s
typedef struct observer
' q  j9 ?. P; J* Q{2 V* A2 J- R. p/ V+ {$ Y
   Object* objectList[num];
; Q1 K+ }/ o8 ^6 w}Observer;$ }" P  S5 t" A

7 W0 T% `, {. K6 L2 e6 J7 O//定义一个观察者模块
3 }( @" b2 r( T( n" iObserver aobserver;& J7 V6 e' v" D$ x
//主函数,创建多个线程来处理不同任务' m' N" t, A: J# f
int main()0 |* H, y3 Q9 O
{
9 A6 V5 J* I+ ]# Y% m8 P1 l! |( \; C2 B//初始化各个观察对象
! F& R$ G2 i' n5 t0 v" E2 e# iaobserver.objectList[0]->update = Update_module1;
3 {& Y! Z( q9 u0 b/ M6 I8 i' Kaobserver.objectList[1]->update = Update_module2;: v- V+ `0 X/ I4 v' V
aobserver.objectList[2]->update = UpdateUpdate_module3;
& G' f) a1 k" a1 l3 b...
5 W) k! {: e. D9 p; T" h/ n: ^  `5 R//线程1(子模块一)- I$ s, D& G' [
create_thread1();
: [7 F% N# i5 c+ z& _//线程2(子模块二)
9 z$ C" k. h: w! k1 V3 G' ?5 ~1 Pcreate_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
...
; V, b2 Y+ ?$ M! Q
) j9 G9 Z5 c4 G+ ^- n$ }}) j; I. {, Z! O- _( K
//串口中断
9 Y' z% A+ H! b2 r& z4 {9 n  Nurat_isr()8 W$ ?8 z% f) n6 Q9 M4 Q
{2 `: Z: S4 ~- Z1 D! u* l  J
...
' K' J( Q6 i5 j1 B2 [/ b for(i = 0; i < num; i++)
9 Z. `5 j( Q& e   aobserver.objectList->update();
) F. K; U9 p: F: l8 p...4 l$ ]4 b- C( w. H( p% X2 {3 u
}
& J. F' g! b- Q' M) T( Z

& O: e- `; f5 \. A, p! V

各个子模块的通知接口,可以像这样子来实现:

//module1通知接口
) k8 z3 b  s5 a6 L- Jvoid Update_module1()' V& r, B0 A2 \. Q$ P
{8 `* y; L6 D5 i( m% T9 K
  //通过设置全局变量来通知子模块一
0 R' V8 {5 l2 M5 w6 \! {8 p  notify_module1 = 1;
6 r3 z  r5 H: E1 x; c; a! K}
4 P! {( p+ p" N# K% j# w
  W# j3 e" q. c% d* q8 ^//module2通知接口- A. {9 P+ K" w8 W6 N) ?
void Update_module2()
5 s1 f& D' c' f0 V9 n+ ^0 N/ m{
2 A% r( n; |0 Y6 p: Q  //通过设置全局变量来通知子模块一& c4 L/ |+ h/ z; D
  notify_module2 = 1;/ S% H: `" y* s9 L1 S$ L  @" T
}
& W3 Z+ A( s2 X8 G) O$ K1 w& m# M4 {' A% _5 M
//module3通知接口- ?. J) h3 X5 A9 _; c& ]0 v% U8 H. P
void Update_module3()
( D- B, i" c4 `* E9 x( k{
: k5 C4 S* |6 s/ L$ Q) j+ a4 `0 M  //通过设置全局变量来通知子模块一: `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

4 K& q* \- K/ G0 n% @3 s% e

: h* W; |( ]* H  T. A0 {  k

这就是c语言中的观察者模式,它可以动态地增加、减少观察对象,解除子模块间的直接耦合,可以很好地预防程序需求发生变化。

但是在实际使用过程中,需要考虑一下开发效率和运行效率问题:

  • 一个被观察者,多个观察对象,开发和调试过程中会稍微复杂一些。

  • 通知函数尽可能不要有太大的运行开销,实在需要进行一些耗时的操作,可以考虑引进类似于Linux系统"中断上下文"这样子的机制。


    ) s2 H) H1 \& |5 \* e

& {& K1 ~0 o  q1 g, |0 e8 f8 p/ w
收藏 评论0 发布时间:2020-5-29 12:15

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版