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

c语言中的观察者模式

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

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


* G- i( U4 m0 U" m9 A$ m* Y& q

模式动机

6 W, Z1 v( _, B4 f# N

观察者模式是比较常用的设计模式之一,尤其是系统里面涉及到多个复杂子系统时,经常会使用到。
1 ]: K% P% O8 h) W0 y

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

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

观察者模式定义:

  • 定义对象之间多对一的依赖关系,保证当被依赖的对象发生变化时,所有的依赖者会被自动更新。
  • 当一个对象需要通知另外一些对象,而你无法预知哪些对象将被通知时,通过观察者模式,克制减少对象的偶合关系。
    ) r) S( j! t9 b* H4 ~9 n
/ L; b  v# R) U; v
场景案例
8 f' D! b- S% X& p$ r8 ?0 @

- u5 g; L& i: L: t& O

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

伪代码实现:

//串口中断/ V* g- p" O9 ^" r; {6 N. v
urat_isr()
/ r; u2 t, g0 J& q/ q% }5 ]{: j( k7 ]& U! l: \2 t2 u1 ^, @
...
# n% v  G7 C" \6 J2 [6 r! g  //通过设置全局变量来通知子模块一
) P0 C3 l. Y8 O  notify_module1 = 1;
  f# z) V$ }& n4 p# _  e% u  //通过设置全局变量来通知子模块二
# s0 k1 k% [% ~; m  notify_module2 = 1;
) w; I# s5 t8 z! I  //通过设置全局变量来通知子模块三
/ T. I: M8 _) |7 \; k  notify_module3 = 1;
( v% O% N8 U+ s* X5 q3 T..., Z1 @, \! W& ?. J- W( R/ \% b" c
}' e$ T0 R6 n( M( E' W) l* Z
9 F/ z- F, @" g8 S1 f5 D
...
$ H- C# o2 z; S8 v$ @//主函数,创建多个线程来处理不同任务4 ^# E4 B7 C% ^
int main()4 D+ o- Y- J' E
{
; G$ i7 v  @6 y0 L7 X  `1 j...
% V6 X1 h; N2 T2 P9 v! c+ F//线程1(子模块一)
# d# b- ]# f" b! w# [; Hcreate_thread1();
: Y! x" a8 i0 Y$ J7 y: N! W//线程2(子模块二)
: p6 P; w: v" wcreate_thread2();5 Z0 z0 ?6 P5 P& A
//线程3(子模块三)
1 ?0 d  I5 H* d) d) a- Q: r2 wcreate_thread3();
% l6 u* T: H0 b, A...' T8 K0 N# Q- L3 T1 V! D. h, t, x2 i
5 a# u4 @0 T0 b; ]$ R) b
}

0 d6 v# b4 a" }6 F9 o' H

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

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


2 U6 o' l5 t5 G* U

改进方案
. O" U& P( W6 S& ~& n8 r/ L

, X% q" a* v. u' J5 |0 q. u! s6 f

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

伪代码实现:

//定义观察对象的数量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
{
, I) E2 t" ?% H( b5 v   //定义观察对象的通知接口
+ Y  [; c6 B6 H+ Y3 J, j   void (*update)();
. y3 R' F5 U: p% {/ m7 E$ [}Object;; L& u( E- ^6 k8 q4 a
- S3 j/ W2 o( G
//定义观察者模块
/ N/ I* z# G  n8 k* u5 _typedef struct observer
  O- `, H( ]7 W! m" V/ @% }1 J# n{
& U# P$ f/ z. V2 G2 }' {   Object* objectList[num];
1 |. ~1 p0 p- F+ [6 n) ?/ K& h}Observer;
0 e7 Y+ E4 D! }: [2 b) v3 G3 }2 C
; b. g0 N* Y! g9 ^. `3 r# S//定义一个观察者模块
8 Q$ Q) u* S$ @9 B6 Z" J, u9 B3 _  EObserver aobserver;
: `" r( z1 Z. v' I/ Y- i//主函数,创建多个线程来处理不同任务
# u$ R) V" w& e) R) v& T2 X. w' uint main()* D5 l. V+ I1 W) R
{: C- B* T/ N: L4 l- W
//初始化各个观察对象
* k& @6 I* [, U! G( l$ ~8 [9 r0 \aobserver.objectList[0]->update = Update_module1;% c* a: u" {4 G# }! h
aobserver.objectList[1]->update = Update_module2;
( w9 P2 J  R+ Daobserver.objectList[2]->update = UpdateUpdate_module3;9 m& C8 W+ w, m( v3 v
...
/ E0 [  ?# ?& e' k: B, {% n8 [//线程1(子模块一)
" J" h, d% }/ q' P# lcreate_thread1();$ D1 q5 F6 ?$ J1 O+ {0 D: L
//线程2(子模块二)
  L; C+ O7 k# F! V) r. a$ H% }% Dcreate_thread2();$ c. S- S( Q$ G' z, }
//线程3(子模块三)$ u4 K& J1 H: f( ~/ s0 R  H  F
create_thread3();
* I+ E& t7 J5 ~. Y+ W$ d...$ P) [, m* f' N6 M3 V

! L; y3 O9 c- E7 g% r# T/ S}
8 E" Z3 e0 ]) b2 h- Z% ]//串口中断
0 x: s' h" z$ J! g' A, Z0 k) Uurat_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();
+ W- F, r, u% o5 M$ |# p...
# S. x. U# \5 _* ~" O% a: h- `* H1 ~}
* S* A6 {7 ^+ a7 w( C: ?' i8 F
2 _8 Z3 a* g4 ^5 K& V, x* o4 O' S

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

//module1通知接口. [8 r6 R4 _" I# N; u7 \
void Update_module1()
8 H9 {! w+ y7 Z9 m! |. B{
$ Q# H5 k' z& H/ F  //通过设置全局变量来通知子模块一0 {0 T% |' c% \! |9 W
  notify_module1 = 1;
7 x: g( \8 n5 S}7 y3 \1 C4 {7 {* M/ n% I% y+ x

  L$ P0 H# ^  @  k/ J3 W" L//module2通知接口' z$ v* A1 v! i
void Update_module2()
) r% p: Y* A4 f" k1 S{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()
( R8 \8 @+ U3 s' v{& [6 S7 w( V* U6 k( d+ Z  c
  //通过设置全局变量来通知子模块一
8 n& m* x; j; H3 E+ ^+ P6 M9 d  notify_module3 = 1;0 o0 h% I" Q/ x" l
}

' Z# @1 k7 l' Q+ Q4 C8 G  b. L
总结$ D, f7 Q' n, n
* q6 _- v' I. {
" I2 Z+ h$ w; k% X+ K4 W' \

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

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

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

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

    3 G2 h; @$ p: }# o: ^
! D. b% P: E( `& K4 k. ^5 u  ~- B
收藏 评论0 发布时间:2020-5-29 12:15

举报

0个回答

所属标签

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