软件架构模式

释放双眼,带上耳机,听听看~!

Garlan和Shaw在《软件体系结构–一门初露端倪学科的展望》(1996)中对软件架构模式进行了分类,这也是目前较为流行的分类方式:

(1)数据流风格:批处理序列;管道/过滤器

(2)调用/返回风格:主程序/子程序;面向对象风格;层次结构

(3)独立构件风格:进程通信;事件系统。

(4)虚拟器风格:解释器;基于规则系统。

(5)仓库风格:数据库系统;超文本系统;黑板系统

温昱在其《软件架构设计》(2007)中推荐了另外一种分类方法,这也是同样于1996年出版的《面向模式的软件体系结构》中的分类方法:

(1)从混沌到结构:分层;管道/过滤器,黑板系统。

“关注各个方面的整体转换,并将工作重点集中于混乱组织成可以结构的问题”

(2)分布式系统:代理者.

(3)交互式系统:MVC;PAC.

(4)适应性系统:微内核;基于元模型。

温昱将前一种称之为经典分类方法,意指其有广泛影响,后一种称为现代分类方法。其指出面向对象开发方法应该和任何一种架构模式都没有直接关系;MVC模式也没提及。他认为经典分类是根据交互机制进行的(调用、发消息或共享数据),现代分类方法是将架构目标作为分类的依据,后一种方式更利于软件架构师挑选合适的架构模式

“但是,绝大多数软件体系结构不能仅依据一个体系结构模式来构建。它们必须支持几种用不同体系结构模式来说明的系统需求。”

1.分层模式:

分层模式是最通用和常见的架构模式。它将依赖性局部化,可替换性好。"治众如治寡,分而治之"。我们的很多组织就是分层模式。分层模式没有明确规定必须的层的类型和数量,不过大部分分层架构模式通常包括4层:表示层,业务层,持久化层和数据库。分层模式是架构中最基本的模式,但是也是我们开发中最被忽视的模式。我们开发中往往没有去定义代码的“层次”,仅仅以“功能”纵向划分模块,没有按实现层次横向切分。

还是以厨房为例吧。

小厨房无所谓了。厨房大了以后,如何管理厨房呢?你当然可以精确管理到每个人,但人的精力是有限的。所以你需要任命一些帮手,有管财务的,有管人事的,有管接待的,有管服务的,有管物资的,当然更要有厨师长,他们先你负责,你分派任务,他们每人可能又有一些助手……

分层模式比较适用于业务逻辑处理。

代码就不重复了,看我的例解嵌入式系统分层结构

分层结构我认为用下图表示比较合适:底层为上层提供服务

软件架构模式

2.管道/过滤器模式:

厨房中的流水线即为此模式典型,先买菜,再理菜,再洗菜,再切菜,在炒菜等……前一个单元对数据进行加工后,后一个单元继续加工。这种模式交互性比较差,适用于复杂性处理。管道/过滤器架构模式貌似分层架构其实不然。分层模式的下一层对上一层的输出进行处理(一般来说是细化),然后将结果返回;管道/过滤器模式的下一单元将上一单元的输出进行再加工。可以这样想有台机器,把一头猪从一端填进去,另一端出来就是的就是火腿肠。分层模式就像送信(TCP/IP协议就是这么解释的):如何把一份信准确及时的送给对方呢?多个阶段就是多个层(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)。管道/过滤器模式适合于单进单出的系统,关注数据处理的系统。

管道/过滤器模式像流水线,下一道工序在上一道工序基础上继续加工

软件架构模式

3.代理模式:

餐厅来客人了,客人需要点菜,直接找厨师吗?有的菜需要辣一些,有的想清淡一些,客人也需要跑到厨房跟厨师说吗?吃完饭,需要买单,客人去找收银台?小餐馆当然无所谓,但是大餐馆如果这样搞, 客人肯定非常恼火,一众厨师肯定也非常头大。餐厅如何做呢?餐厅的服务生来搞定。服务生起到代理人的作用。有这样一种情况,如果来了一个客人,这个客人说的话服务生听不懂,这怎么办?我们想到的是找一个翻译,翻译起到在客人和服务生之间交流的中介作用,这时候采用的是何种工作模式呢?这不是代理模式也不是管道/过滤器模式,而是分层模式。在软件开发过程中,我们也经常会遇到类似的情况,有部分功能我们可能需要第三方的开发包,但是又不希望被“绑死”在特定的第三方上,这时候我们可能会自定义接口,将第三方屏蔽,设计模式中称这种模式为适配器模式。

代理模式把下图中的9种关系变换成6种关系:

软件架构模式

4.MVC模式:

MVC表示Model、View和Controller。这三种组件通过交互来协作。View创建了controller之后,Controller根据用户交互调用Model中的服务,而Model会将自身的改变通知View,View会读取Model的信息更新自身。MVC模式比较适用与GUI程序。还是以厨房为例:厨房中有某道菜非常有名,客人都很喜欢点这道菜,服务生也经常先客人推荐这道菜,但是如果某些原料没有了,这道菜就不能做了,这时服务生要向客人说明情况,采购人员可能需要采购新原料,厨师可能不在做前期准备,客人可能暂时不再点这道菜…….在点菜、采购、订单、制作等活动他们之间是相互影响的,他们之间如何协作呢?一一通知吗?显然工作量太大。可以用让一个人管理一个公告牌或者小黑板,记录原材料的数量等,厨师每取一份材料就改变一次公告牌内容,一旦取完了,服务生就应该提醒顾客某道菜不要再点了,采购人员发现原材料不足,也会主动采购新材料。而服务生发现原材料充足,也会向顾客推荐,同时在顾客点单的情况下,在公告牌通知预留一份材料。

如果不这么做呢?可能是如下情形:服务生把菜单给厨师,厨师去取原料,发现不足,厨师通知服务生,告诉服务生某道菜没法做,厨师可能还要通知采购新材料。服务生然后告知顾客,某道菜材料不足,没法做。采购来原材料后,采购人员通知服务生可以告知顾客重新点了,顾客点单后,服务生再通知厨师做菜….非常的繁琐,但是可怜的是,我们在外用餐时还会经常遇到此类情况。

在这里公告牌就相当于Model,如果信息比较多,可能的情况下,甚至安排专门人员管理这个公告牌,这个人就是View,他负责如何显示信息。参与就餐活动的人会影响Model的内容,而所有人因公告牌内容而采取一系列的活动,这就是Controller,Controller接收输入。用户通过控制器改变了模型数据,模型的数据发生了变化要通报所有视图,视图从模型中恢复数据并更新所显示的信息。区分出系统中哪些是需要“人”来操作的,哪些是用来单纯“输出信息”的,哪些是内部的状态,就是构造MVC系统的关键。

看一个例子:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
1class  Model{
2 public:
3      int  data;
4      string msg;
5      void setData(d){
6          this.data=d;
7          cout<<"get data"<<endl;//业务处理
8          msg+=d;
9      }
10     int getData(){
11         return this.data;
12     }
13 };
14
15class Controller{
16 private:
17     Model model;
18     View view;
19 public:
20     Controller(Model m,View v){
21           this.model=m;
22           this.view=v;
23     }
24     void setData(data){
25this.model.setData(data);
26}
27     void setMsg(msg){this.model.msg=msg;}
28     int  getData(){return this.model.getData();}
29     int  getMsg(){return this.model.msg;}
30     void updateView(){         this.view.output(this.model.data,this.model.msg);
31    }
32 };
33
34class View{
35 public:
36      void  output(data,msg){
37          cout<<data;
38          cout<<msg<<endl;
39      };
40 };
41
42int main(){
43    Model model= new Model();
44    View view= new View();
45    Controller controller= new Controller(model,view);
46    controller.setData(10);
47    controler.updateView();
48    controller.setMsg(“hello”);
49    controler.updateView();
50    return 1;
51 }
52
53

*上图来源于菜鸟,以上代码也是模仿其编写的。

软件架构模式

上图来源于经典软件架构,补充了MVC模式很多细节。

我的感觉两个图和代码一起看比较合适。

若不用MVC模式呢?


1
2
3
4
5
6
7
8
9
10
11
12
13
1int main(){
2     int  data;
3     string msg;
4     data=10;
5     cout<<data;
6     cout<<msg<<endl;
7      msg=“hello”;
8     cout<<data;
9     cout<<msg<<endl;
10       return 1;
11}
12
13

当数据量多的时候,如何修改呢?有重用性吗?要改变显示样式有如何改呢?

既然MVC的主要用途是GUI系统,再举一个例子,模拟音乐播放器:

软件架构模式


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1<!DOCTYPE html>
2<html>
3<head>
4<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
5<script src="myscript.js"></script>
6<style>
7#bar{
8height:10px;
9background-color:darkcyan;
10}
11#rate_of_progress{width:200px};
12</style>
13</head>
14<body>
15<table>
16<tr>
17<td>歌词</td><td id="content"></td>
18</tr>
19<tr>
20<td id="pre">上一首</td>
21<td id="play_pause">播放</td>
22<td id="next">下一首</td>
23<td >进度</td><td id="rate_of_progress"><div id="bar"></div></td>
24<td id="recycle">列表循环</td>
25</tr>
26</table>
27</body>
28</html>
29

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
1//myscript.js
2$(document).ready(function(){
3//模型
4var Model={
5state:"pause",
6_rate_of_progress:0,
7_current:0,
8_recycle:0,
9_contet:["1....","2....","3....","4....","5....","6....","7....","8....","9....","10...."],
10pre:function(){
11  this._current--;
12  if(this._current<0)this._current=9;
13  this._rate_of_progress=0;
14},
15play_pause:function(){
16  if(this.state=="pause"){
17    this.state="play";
18  }
19  else{
20    this.state="pause";
21  }
22},
23next:function(){
24   this._current++;
25   if(this._current>=10)this._current=0;
26   this._rate_of_progress=0;
27},
28rate_of_progress:function(){
29  if(this.state=="pause")return;
30  this._rate_of_progress++;
31  if(this._rate_of_progress>=100){
32    this._rate_of_progress=0;
33    if(this._recycle==0)
34        this.next();
35  }
36},
37recycle:function(){
38   this._recycle=1-this._recycle;
39},
40};
41//视图
42var View={
43View:function(){
44  if(Model.state=="pause"){
45    $("#play_pause").html("播放");
46  }
47  else{
48    $("#play_pause").html("暂停");
49  }   
50  if(Model._recycle==0){
51    $("#recycle").html("列表循环");
52  }
53  else{
54    $("#recycle").html("单曲循环");
55  }
56     
57  $("#bar").width(Model._rate_of_progress+'%');
58  $("#content").html(Model._contet[Model._current]);
59},
60};
61//控制器
62var Controller={
63timer:null,
64ctl:function(e){
65   switch(e.target.id)
66   {
67  case "next":
68      Model.next();
69        break;
70  case "pre":
71      Model.pre();
72        break;   
73  case "play_pause":
74      Model.play_pause();
75        break;
76  case "rate_of_progress":
77  case "bar":
78            Model._rate_of_progress=e.offsetX/$("#rate_of_progress").width()*100;
79        break;   
80  case "content":
81     Model.content();
82        break;           
83  case "recycle":
84     Model.recycle();
85     break;
86   }
87   View.View();
88},
89};
90Controller.timer=setInterval(function(){
91  Model.rate_of_progress();
92  View.View();
93},100);
94//控制器与输入关联
95$("#content").click(Controller.ctl);
96$("#next").click(Controller.ctl);
97$("#pre").click(Controller.ctl);
98$("#play_pause").click(Controller.ctl);
99$("#rate_of_progress").click(Controller.ctl);
100$("#bar").click(Controller.ctl);
101$("#recycle").click(Controller.ctl);
102});
103

5.状态模式:

状态模式来实现状态驱动行为,主要是用来实现有限状态机的。在GOF中将其放在设计模式中,认为其是一种行为模式,我认为将其放在架构模式中更好:各种状态相互转化,表示状态的元素构成了一系列“环”。

现实生活中个人、组织也是经常在各自状态之间进行切换的,在不同的场合切换不同的身份(或状态)。"实现有限状态机的一种方法是使用一系列的if-then语句或switch语句稍加整理。虽然初看之下,这种方法是合理的,但当实际应用到任何一个稍复杂的情况,switch/if-then解决方法就变成了有一个怪物,随着更多的状态和条件的加入,这种结构就像意大利面条一样跳转得非常快,使得程序流程很难理解并且产生一个调试噩梦。此外,它不灵活,难以扩展超出它原始设定的范围"。"一种用于组织状态和影响状态变换的更好的机制是一个状态变换表"。"另一种方法就是将状态转换规则嵌入到状态本身的内部"。(引自:[美]Mat Buckland,《游戏人工智能编程案例精粹(修订版)》,人民邮电出版社,2012),以下的代码也为摘抄或模仿上述文献编制:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1template<class entity_type>
2class State{
3   public:
4       virtual void Enter(entity_type*)=0;
5       virtual void Execute(entity_type*)=0;
6       virtual void Exit(entity_type*)=0;
7       ~State();
8};
9template<class entity_type>
10class StateMachine{
11   private:
12       State<entity_type>* m_pOwner;
13       State<entity_type>*m_pPreviousState;
14       State<entity_type>*m_pCurrentState;
15       State<entity_type>*m_pGlobalState;
16     public:
17          void StateMachine(entity_type *owner):m_pOwner(owner),m_pCurrentState(NULL), m_pPreviousState(NULL){}
18          void SetCurrentState(State<entity_type>*s){m_pCurrentState=s};
19          void SetGlobalState(State<entity_type>*s){m_pGlobalState=s};
20          void SetPreviousState(State<entity_type>*s){m_pPreviousState=s};
21          void  Update()const{
22              if(m_pGlobalState)m_pCurrentState->Execute(m_pOwner);
23              if(m_pCurrentState)m_pCurrentState->Execute(m_pOwner);
24         }
25         void ChangeState(State<entity_type>*pNewState){
26             m_pPreviousState=m_pCurrentState;
27             m_pCurrentState->Exit(m_pOwner);
28             m_pCurrentState=pNewState;
29             m_pCurrentState->Enter(m_pWner);
30        }
31       void RevertToPreviousState(){
32            ChangeState(m_pPreviousState);
33      }
34 };
35

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1class E {
2   public:       E(){m_data=0;m_pStateMachine->setMachine(this);}
3       void Update(){
4           ++m_data;
5           m_pStateMachine->Update();
6          }
7       StateMachine<E>*GetFSM()const{return m_pStateMachine;}
8       int m_data;
9  private:
10      StateMachine<E>*m_pStateMachine;
11 }
12 class State1:public State<E> {
13    public:
14        void Execute(E *a){
15            cout<<"s1="<<a->m_data<<endl;
16            if(a->m_data%3==0)              a->GetFSM()->ChangeState(new State2());
17            }
18       void Enter(E *a){};
19       void Exit(E*a){};
20  }
21 class State2:public State<E> {
22    public:
23         void Execute(E *a){
24            cout<<"s2="<<a->m_data<<endl;
25            if(a->m_data%5==0)              a->GetFSM()->ChangeState( new State1());
26             }
27       void Enter(E *a){};
28       void Exit(E*a){};
29 }
30
31int main(){
32   E e;
33   for(int i=0;i<200;i++){
34       e.Update();
35       sleep(200);
36    }
37 }
38

为更进一步解释状态模式,下面列举一个相对比较复杂的例子:模拟电子手表。电子手表只有4个键,其中一个键主要起照明作用,电子手表功能由其余三个按键实现,但是却要设定时间、日期、闹铃等功能,逻辑关系比较复杂,如果不采用状态模式设计的话,代码的实现会非常的困难。因为代码量相对较大,不在此展示,具体见模拟电子手表–结合MVC模式和状态模式

给TA打赏
共{{data.count}}人
人已打赏
安全经验

Google Adsense广告设置到位置放置的技巧

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索