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

c语言中的观察者模式

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

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


% h0 C& z4 x% D. l

模式动机


) x0 b! ?0 ]( |9 B& }1 d& K+ I+ d

观察者模式是比较常用的设计模式之一,尤其是系统里面涉及到多个复杂子系统时,经常会使用到。
" o& s' V( e9 R5 M2 c' p* k

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

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

观察者模式定义:

  • 定义对象之间多对一的依赖关系,保证当被依赖的对象发生变化时,所有的依赖者会被自动更新。
  • 当一个对象需要通知另外一些对象,而你无法预知哪些对象将被通知时,通过观察者模式,克制减少对象的偶合关系。
    $ _/ {+ Z: H6 Z9 [- }
: S. y" U( h% B/ q, Z1 X# ^
场景案例
% _6 @+ e( F$ C; P

' {- ]0 o% v1 R: w! q- t

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

伪代码实现:

//串口中断( ^( m; I. _' u2 s/ h  j) s0 n; X
urat_isr(): O6 p- o1 I* u) U! U* I3 o( s
{
4 Y( l! Z5 H2 w0 O2 N7 z1 |" j...
9 ]1 ]' v5 B( U% B# _  //通过设置全局变量来通知子模块一
9 i0 O, \) [  P. `6 V  notify_module1 = 1;
/ S5 M2 S; C, A# }) t9 R; O/ V( G  //通过设置全局变量来通知子模块二
8 _$ B( |/ g( G  notify_module2 = 1;
  K- I- h) ?, S# c7 @  //通过设置全局变量来通知子模块三
  h, I5 S# s1 m' |/ n  notify_module3 = 1;4 l5 d5 f0 }, X5 ]$ ?, ]
...
! @" d4 y9 q2 `) T}) V4 R- {  M( ^4 z7 r! d2 D: V( P

$ N8 |+ @. T+ Q, V...$ d' N3 h% m* E4 E% q: L# j# a; C& |
//主函数,创建多个线程来处理不同任务% `& X' {( K$ e% h; U
int main()
; U& T/ r0 E3 i! C$ N; \: W{7 A; ?6 ]9 A$ ], T, R1 n
...! C( a8 H5 A  a  N5 P
//线程1(子模块一)
3 b9 x% m. L- ucreate_thread1();/ a0 c& B5 m; q
//线程2(子模块二)# W  |* `# K: I) v8 [" g/ I% R
create_thread2();
7 j; S9 K& M: o; e) y* U6 @1 O//线程3(子模块三)
. w; G+ y4 J. s- Screate_thread3();/ Y  `  G; \1 n; |; Y3 z6 x' z
...
% S! R  c6 U. x- ~3 v8 P0 G. W
0 [% K& X* w" m! q- i, P9 u}
* ~8 [8 @, ?& x' t( ^

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

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


4 ~+ s9 F7 ^! q5 f

改进方案6 d  u8 c' W" x1 i- T- p
4 c! A/ ]4 @9 n- A6 M

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

伪代码实现:

//定义观察对象的数量
/ _6 [6 y9 U* @: u/ v' `#define num 3  g7 l; X2 J0 g# f% e
' k. A6 z3 [5 w+ D4 E
//定义观察对象
- W( Q* U8 L" W0 Jtypedef struct object
/ K! q$ G  L3 }{
7 s2 y3 `& _$ A; w/ g' o# g" M: j   //定义观察对象的通知接口1 g8 F7 ~. b; m6 _* n5 T0 A
   void (*update)();
; o9 @  |( }4 U0 }* z5 B}Object;7 |" |3 [$ M) u0 Q: o

% s$ {. e1 `1 @6 T. D) B+ c- S//定义观察者模块
5 F0 A2 Y& g! Z/ o# Otypedef struct observer
2 `* H8 }. H, s) C+ r8 M' p4 Z. I- i{; E3 S$ h7 g  `% Q# }( c
   Object* objectList[num];# h( X1 }, t8 x
}Observer;0 o% S/ w4 E" \5 F
$ |  ]$ I* I* H3 n: Q, Q
//定义一个观察者模块+ h0 X1 h; \, a2 a& D1 ^) i
Observer aobserver;
) @* ?, _1 j# e% N1 v3 L: j5 L//主函数,创建多个线程来处理不同任务
' I0 {) M# G) N+ W6 t% t4 V4 D. o  ~int main()5 N" Z2 Y- r! p& X3 m
{$ ~  ?5 O& z! v# L3 _& V
//初始化各个观察对象
* @8 Q) ?7 Y& X. Oaobserver.objectList[0]->update = Update_module1;
/ T: T, a1 i$ d5 u, maobserver.objectList[1]->update = Update_module2;
# r0 I1 {2 B! `6 r; ^8 T- ]aobserver.objectList[2]->update = UpdateUpdate_module3;
4 e$ P! V) ?( d  I8 a& y...; J' O! s3 ^8 a0 d- f
//线程1(子模块一)$ z8 F6 F: K7 Y3 o$ x
create_thread1();
6 _) N! X% s/ D$ r. d" ^1 [4 ?//线程2(子模块二)+ o" v' g3 C/ j% |5 X9 F
create_thread2();
0 p* n( C' v+ {2 N//线程3(子模块三)
$ ], i1 Q1 x7 q( F2 n3 e% {create_thread3();
4 `" r/ z+ A- L6 j8 i: B9 u4 m7 g...& q% }9 y6 V( s& f
/ L3 Q, M& w8 I
}6 ]' n" _. P$ U
//串口中断& {+ p' Q- H$ W& n
urat_isr()5 O. D- Z, r1 j1 N  \5 S- Z# t
{' R$ M) {$ n6 J6 v  V' p5 \9 ]; h
...
' R, l3 F, B5 c# w, C for(i = 0; i < num; i++)% t% U( I5 a& d. K* M, Y# H1 D
   aobserver.objectList->update();& d! ?$ e. F# I4 R' X
...- |8 y, _& Z- ~$ i" V: D
}
0 b9 i0 s% N; T, ?; T: k; g$ N
* x$ u1 `* N% S

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

//module1通知接口
1 z: f  ^. ]0 p( U( Q. c" `void Update_module1()
( ]! P# A8 O0 a/ n{; Z) T+ |9 h* U# k) L7 f
  //通过设置全局变量来通知子模块一& K1 s) `9 n8 m; c- T% {# g8 \3 z: c
  notify_module1 = 1;
5 l3 L7 ]) V$ E7 ?# _! z4 D}$ n% j% o. W* x9 ~9 A

  a5 x, |# E" m' Z+ p( [//module2通知接口& W  c+ o' d0 t+ H; O* O# g! i
void Update_module2()0 @6 x$ a0 c- }5 l$ A. M
{- l! z. ], O2 r' _
  //通过设置全局变量来通知子模块一
( ~, [" G+ u3 T7 G; K& D. _  notify_module2 = 1;+ x5 f6 ?& x0 ]: O
}2 O' X% z/ [+ G

7 W/ U8 K" `7 b" i; K- f//module3通知接口
' [" N  m. Q* I$ U  @/ mvoid Update_module3(): a4 t' E' Q5 j. }
{
0 k" N5 Z$ `/ f$ M9 X0 k  //通过设置全局变量来通知子模块一
6 R" W+ b( X0 G1 {2 z  notify_module3 = 1;
* N% {/ Z: u* F; r6 O}

$ n' p8 u1 z3 E9 a' y1 e# L, X
总结3 h; E  b6 ^5 b' n* q

( c; a; \! g5 y/ M6 ?6 L9 v
3 H% ?/ Q) m# ]( U; c

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

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

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

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


    1 S# z: C, w5 Y2 _
0 c' |) j+ ?6 J  Q0 o  N4 e
赞 收藏 评论0 发布时间:2020-5-29 12:15

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版