不只一次听到不只一个人说,设计模式就是那么回事,怎么怎么回事,那么那么回事,大致都带着肯定的口吻——表达的不是设计模式如何而是听着应该屈从于说者的权威,不容置疑的自信,也带着那么丝不确定。
我觉得设计模式是软件行业很不错的书,而软件行业的好书基本上都是国外出版,好书就是丰富经验的积累和说明,讲着故事和经历,阐明原理和规律。所以我更关心我如何理解设计模式,如何应用设计模式。
最初那段时间,完全是设计模式的粉丝,非模式不代码,特别是新的软件无论大小,都要往模式上靠——就像OOAD的作者说的,拿着钉锤的人眼中世界全是钉子,总想敲敲打打一番——最恰当不过了。
设计模式的基础是OO,流传着这样的观点:大部分的C++开发者,用C++写C代码(C++之父),以及OOAD作者说的“给一个不懂得如何使用电钻的家伙,他可能会拿它当钉锤——结果当然是不那么好用,而且大多会砸到自己的手指头”。所以,我发誓,要好好学习OO和C++。
认知是层次性的,而且会不断反复。状态模式很早就想写,但又没有什么好写的,终于有两三个满意的软件用状态模式解决服务器状态问题,我想,要总结一下,给自己以后使用这个模式时更快进入角色,也更方便给别人讲解。
服务器必然有状态,有服务器本身的状态,还有客户端的状态。当状态简单时,就不必用模式了,譬如1万行的简单服务器,这个复杂度OO都不用,直接用几十个函数和变量表达就OK了。但对于稍复杂的状态,状态模式大有可为。
先总结如下:
1. 复杂状态变换,才需要使用状态模式。否则就直接用变量表示吧。
2. 只要服务器的状态能分析清楚,状态模式就一定能解决问题。即只要状态可知,就能在预期范围内变化(即实现功能)。
3. 状态模式可以稍作修改:状态模式中,改变状态的动作是一个函数,对于很多状态而言,函数和类都会剧增,所以可以用一个函数,动作用参数表示。
4. 状态模式应关注的是当前状态改变为其他状态时的情况,而不要关注当前状态的上一状态是什么(实际上是等同的,但前者会简单,或者很复杂)。
考虑典型的多进程模型(http://blog.csdn.net/winlinvip/article/details/7764526),若系统有三个状态:ready,reading,stopping,在master中管理状态:
// master process block_all_signals() for(;;){ sigsuspend(); // get a signal. if(state = reading){ do_read(); } else if(state = stopping){ exit(0); } else{ // ready. } } void signal_handler(...){ if(READ){ state = readding; } else if(STOP){ state = stopping; } else{ state = ready; } }
用state变量表示当前的状态,没有状态转换的概念(没有上下文)。
若使用修改过的状态模式表示如下:
// master process block_all_signals(); for(;;){ sigsuspend(); // get a signal state->do(); // state work } // state change void signal_handler(...){ if(READ){ state->action(READ); } else if(STOP){ state->action(STOP); } } class ready_state{ void action(command){ // ready=>reading if(command = READ){ set_state(reading_state); } // ready=>stopping else if(command == STOP){ set_state(stopping_state); } } } class reading_state{ void do(){ //do reading.... // change to ready set_state(ready_state); } } class stopping_state{ void do(){ exit(0); } }
状态模式实际上在state中保存了当前的状态,在signal_handler中进行状态转换,在for(;;)主循环中执行状态应该做的事情(避免在signal_handler中做过多操作)。实际上它实现了以下状态转换图:
init => ready => reading => init
=> stopping => exit
状态转换的意义在哪里?在于定义了可能的状态图,即有很多assert,当系统异常时就会触碰这些assert从而告知我们准确的发生了什么不应该发生的事情。而且转换图实际上清晰定义了系统的状态:只有熟知的系统才能很好的实现,只有清晰的定义才能表明我们已“熟知”此系统,不清晰的定义即模模糊糊的边界,在复杂系统中就是一种灾难。
举个稍复杂的转码服务器状态变换以及状态模式实现的例子,系统使用单线程实现,轮流处理多个任务,包括以下状态:
- ready:任务的准备状态。
- transcoding:正在转码。
- reporting:转码任务完毕汇报。
- error:转码任务出错。
- canceling:正在取消任务。
- removing:正在删除任务。
- finishing:任务转码完毕。
这些状态和每个任务相关,轮流处理任务时,从任务的状态开始转换。状态转换图:
主循环代码:
for(i=0; i < task_nums; i++){ task = get_task(i); if(task.state.do()){ // completed. remove_task(task); } } class read_state{ bool do(){ if(cancel){ return set_state(cancel_state); } if(remove){ return set_state(remove_state); } return set_state(transcoding_state); } } class transcoding_state{ bool do(){ // do transcoding..... if(cancel){ return set_state(cancel_state); } if(transcode_ok){ return set_state(reporting_state); } if(transcode_error){ return set_state(error_state); } } } class canceling_state{ bool do(){ // do cancelling... return true; } } class error_state{ bool do(){ // do error cleanup... return true; } } class removing_state{ bool do(){ // remove task resource... if(remove_error){ return set_state(error_state); } return true; } } class reporting_state{ bool do(){ // do reporting... if(cancel){ return set_state(cancel_state); } if(report_error){ return set_state(error_state); } return set_state(finishing_state); } } class finishing_state{ bool do(){ // do finishing... return true; } }
若使用状态变量来表达状态转换,会不直观,而且比较麻烦了。
若使用多进程模型,master在sigsuspend可能会收到多个信号,而且一般在信号处理函数中只是改变状态,在主循环中action。此时用state模式更加自然和处理完善,因为此时的模型会更复杂。
几个理解:
- 状态模式在表达异步状态时表现优越,异步状态本来就是包含了较长状态的含义,相反非异步操作用函数表达就可以了。
- 状态模式同样可以表达网状的状态转换,这在表达复杂的步骤时很有用,譬如A的下一步能到C或D,C下一步是D或E,D下一步是A或C,这样复杂的路径转换。
- 状态模式必须要有归属,即表达谁的状态,是谁的状态。若很模糊,可能没有抽象好,或者根本就不应该用状态来表达。