|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
9 e, ~5 i7 ]1 @; M* O
前言:5 k, r- `& h! O
$ S6 ?! i1 o. u) C3 u; H+ `
最近在学习《Unix编程艺术》。以前粗略的翻过,以为是介绍unix工具的。现在认真的看了下,原来是介绍设计原则的。它的核心就是第一章介绍的unix的哲学以及17个设计原则,而后面的内容就是围绕它来展开的。以前说过,要学习适合自己的资料,而判断是否适合的一个方法就是看你是否能够读得下去。我对这本书有一种相见恨晚的感觉。推荐有4~6年工作经验的朋友可以读一下。
# h) F d( t0 v9 ^& s* a3 V( P: z2 T+ Q% h3 n8 F9 }+ N: ~
正题:
$ W( |1 W+ f; v
. H; r8 e8 H1 b$ z* f作者在介绍Unix设计原则时,其中有一条为“表示原则:把知识叠入数据以求逻辑质朴而健壮”。结合之前自己的一些经验,我对这个原则很有共鸣,所以先学习了数据驱动编程相关的内容,这里和大家分享出来和大家一起讨论。
1 ~# q/ z" T3 [$ ]( P2 L& W6 t/ h6 I0 T. x6 \) @% B
数据驱动编程的核心5 p7 t6 Z( Q) |! D
3 }9 Q0 p0 y! w
数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。
0 M& d" y- B5 B E U; v
$ q+ h2 G2 v R( j C真的是这样吗?让我们来看一个示例。
x& Z, e1 O7 J7 K( D& h
# g, l" O* B3 T: m* n1 L. F假设有一个程序,需要处理其他程序发送的消息,消息类型是字符串,每个消息都需要一个函数进行处理。第一印象,我们可能会这样处理: # k5 i. l1 a, V0 g
void msg_proc(const char *msg_type, const char *msg_buf)
; P$ P/ I- k. o. P, j2 v8 _" e, U{
! x& d9 u) a# }5 e5 L' k if (0 == strcmp(msg_type, "inivite"))
3 A) [2 l* ]* l0 q {
8 @" }( b9 o/ z9 W# A+ e+ S8 z. @' _ inivite_fun(msg_buf); , N5 m9 O# E% q9 Y7 E
}
: g% a. |0 h+ F8 }3 m# M7 S else if (0 == strcmp(msg_type, "tring_100"))
1 H8 K2 |4 H. g, R6 m$ S) u4 | { 0 N( z6 ?9 R, H) c" Z
tring_fun(msg_buf); & ^8 r9 x, X$ F9 ^. L( f
}
! f! R$ E7 \ L else if (0 == strcmp(msg_type, "ring_180"))
5 }% ~; F0 X5 a5 q3 V { 8 @- _4 C% b8 S* P$ u
ring_180_fun(msg_buf);
F" k! v* }! X/ J& z } 5 l$ S& y: X9 ~& ^$ f5 [) K7 c4 b( d
else if (0 == strcmp(msg_type, "ring_181"))
2 G; O8 y& Q& x" f% ~ {
5 H# I0 T! S6 u: ? ring_181_fun(msg_buf); 5 K h$ x" m* ^* Q, i" L
} 0 l2 a/ `% w2 r! E
else if (0 == strcmp(msg_type, "ring_182"))
: g% \0 ]* W. D+ q9 j W, \4 a {
% D0 ~- p" J3 H/ f ring_182_fun(msg_buf); / n6 {, M* N8 ^- u
} ) [) V0 _# `# G c( `6 Y3 p
else if (0 == strcmp(msg_type, "ring_183")) , ]" @) g+ h# n2 `
{
8 A7 w* p: P$ q8 i6 m! d j ring_183_fun(msg_buf);
# p2 Q C3 `9 I8 m" g+ v1 ~) N }
1 T7 V' f9 ~9 o+ \% e else if (0 == strcmp(msg_type, "ok_200"))
( E. F4 ~8 a9 l' k/ ` { + m- W; p( A1 E7 m
ok_200_fun(msg_buf);
) J$ ?6 _$ H* n! J0 V+ ^7 d }
# S: B4 n0 L; s" Q+ ^
% f3 Q. f# ?) |% \0 D 。。。。。。 4 Q. M. ^- H/ x4 P7 o
else if (0 == strcmp(msg_type, "fail_486"))
* ~2 B6 D0 h e( ]' N1 U8 Q- @ {
1 S* x2 L8 u2 a- | fail_486_fun(msg_buf);
1 Q8 q0 w4 h/ }8 z5 ~ }
" Q' G, ]" U; c5 _1 J, Y) q" I; M else / l/ }- D% g2 y" M6 N
{
1 i) r% p* Y6 t3 K+ S$ R: s log("未识别的消息类型%s\n", msg_type); + E2 `6 O1 Y, i
} 6 Q7 P1 `3 W4 Z& _( k
}
5 u3 P) C. w8 r+ c1 R上面的消息类型取自sip协议(不完全相同,sip协议借鉴了http协议),消息类型可能还会增加。看着常常的流程可能有点累,检测一下中间某个消息有没有处理也比较费劲,而且,没增加一个消息,就要增加一个流程分支。' Q4 h0 i- B& k- G0 W8 `
7 U6 c' y) g# B# t* Y按照数据驱动编程的思路,可能会这样设计:
. Y0 g8 k u/ }! Q4 _ F" O' G+ Utypedef void (*SIP_MSG_FUN)(const char *);4 \( s a r+ J
: n9 r! C# h" f0 Ptypedef struct __msg_fun_st 5 O$ A9 {6 A+ {0 @; X
{
. H2 {, @* h' A9 V" S" p const char *msg_type;//消息类型
& V3 l, x% X5 v2 b/ ]1 F/ G+ h- ] SIP_MSG_FUN fun_ptr;//函数指针
4 M: o7 E1 U0 z}msg_fun_st;! F+ F% `, E' o2 k" l- W
% k1 A5 d' R+ q" Y7 |: W( gmsg_fun_st msg_flow[] =
2 N' p; g" |/ V2 l L1 ^{
* u1 I; s/ d, }: s7 H {"inivite", inivite_fun}, 9 k1 n& Q+ D0 S" X: ^+ C% D, c: o
{"tring_100", tring_fun},
8 Q/ \3 _6 v* z {"ring_180", ring_180_fun},
m& W7 U6 a# r( }: z {"ring_181", ring_181_fun}, $ E' ` l0 j2 {
{"ring_182", ring_182_fun},
v% r: }8 l0 A5 f {"ring_183", ring_183_fun},
0 F/ b% C; r M+ I' {: j {"ok_200", ok_200_fun},0 R6 F% X2 a- E h5 v2 j1 U
2 p9 S& ^0 U, |' X 。。。。。。
- M( W* }" c" G, Y {"fail_486", fail_486_fun}
8 i6 j0 J9 R6 g( L4 X- q# ]" l( Z};& L ]+ D. d' e
! v8 \# ]" X: w$ m- Jvoid msg_proc(const char *msg_type, const char *msg_buf)
) B7 E+ V, w$ U+ ~{
# u+ s, Y7 G% K- @0 e& H% b3 B int type_num = sizeof(msg_flow) / sizeof(msg_fun_st); + `1 P1 e L ]3 W
int i = 0;( u$ Z2 {4 \6 N/ b S ?
, D4 Y/ i1 s! T% h for (i = 0; i < type_num; i++)
. @7 R8 t& p( P: G5 R- Z { ; G' N8 i+ b8 l4 J# ^# s& x h
if (0 == strcmp(msg_flow.msg_type, msg_type)) 0 n+ U5 W) a6 E: v! x$ X) Z
{ # G: P z9 `% n; m3 {$ ~$ L2 g
msg_flow.fun_ptr(msg_buf); + S# I# Y+ v, W$ V7 k
return ;
3 O: I) X0 v6 T! n$ l( r; F0 P } 1 o& V' g w. w# U- \
} ( X6 ^0 v: {; u" O# D- s3 Q Q
log("未识别的消息类型%s\n", msg_type); ; Y0 u C5 e. u
} * f- l8 k- t8 ]' N
. Y* p- m- ^) z
下面这种思路的优势:
; D/ j1 c/ U; I$ X& N) l/ m: ]
) g: B1 O/ P5 s/ B9 Z1、可读性更强,消息处理流程一目了然。
9 O; @+ z" [8 _, B, s7 d
1 N$ K9 i+ w# N8 g2、更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。
- u" `( X0 o! p m+ r% j( w& @; V- |6 N
3、重用,第一种方案的很多的else if其实只是消息类型和处理函数不同,但是逻辑是一样的。下面的这种方案就是将这种相同的逻辑提取出来,而把容易发生变化的部分提到外面。
# W- @6 G/ [# K/ U; B$ |. m6 S) C# j, d, Q
隐含在背后的思想:
( l1 q5 Y) d- o b# d- k% M& c2 G6 |9 I. [# t5 g
很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:6 S `: Z4 |3 Y: y
' K+ F/ P' H% N% R7 X1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
2 x8 _2 e: z) I' Y6 ^
1 ^; U3 Q" w/ q( b) @& c/ W2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。
/ M w. B$ r; v5 T
% w: {7 I3 e# y( X8 Z$ M. m& P3 _7 u3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。% `3 o) A: E) Q$ c: n( W5 }
% `! T q8 R3 q) N4 h
数据驱动编程可以用来做什么:, {9 U7 C: S" ~9 {! q* J% [
4 x1 ]9 |5 x2 [3 f
如上例所示,它可以应用在函数级的设计中。) b8 r( i, J2 u3 ]$ b; t0 B1 c
! b; K# [! G' Q4 L' O- _9 I同时,它也可以应用在程序级的设计中,典型的比如用表驱动法实现一个状态机(后面写篇文章专门介绍)。
: M& L- T4 B% H
/ v; N7 X+ W" w5 J也可以用在系统级的设计中,比如DSL(这方面我经验有些欠缺,目前不是非常确定)。- h' d6 x5 q* I3 W( t; o
. E: {0 g; u9 C
它不是什么:
; t9 ?8 W2 @3 e, @- y
5 I$ H8 r0 _' E, @( C. d S. ?1、 它不是一个全新的编程模型:它只是一种设计思路,而且历史悠久,在unix/linux社区应用很多;" |$ j0 }+ _7 a8 t: p1 W1 m
* ^! T! e, v. D0 ^0 c7 d( w) X2、它不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程;OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”# x* B. \' k. e/ Y
. T+ W( Y8 ], x
书中的值得思考的话: l+ X% X2 E, Z$ C7 b7 Y" p: o
9 q/ T! S3 ]7 e数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike
; e$ d+ y& j/ ~$ S5 c" q& i4 S% o2 Q4 _# |+ @# T# N0 D7 _' }
程序员束手无策。。。。。只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks
$ T$ ]: ]7 b7 e- k
) v: g) L2 D, X数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。 |
|