开通博客已经很多年了,习惯性的看别人写的技术博客,自己确从来没有写的打算。原因有两种,一是觉得写一篇好的博客确实要花费很多时间,时间=金钱吧,毕竟不是啥大师,能够做到信手拈来。二是觉得才疏学浅,写的博客有问题,怕被人拍砖,毕竟技术人员嘛,不会忽悠,有的就是自己这几年敲代码的经验,被人鄙视后,总会觉得自己一无是处吧。
不过最后还是觉得开始养成写博客的习惯,毕竟项目一个个做了,总想有点念想,将自己这几年项目的中用到的设计模式,软件架构之类的东西,总结一下。本人主要是.NET和java开发,今后也会陆陆续续把积累商业项目的工具包源码传到github上去。供大家参考吧。
这次谈一下设计模式中的命令模式。命令模式是一个高内聚的模式,其定义为:Encapsulate a request as an object, thereby letting you parameterizeclients with different requests, queue or log requests, and support undoableoperations.(参考-设计模式之禅)。命令模式的通用类图如下所示:
对于命令模式的理解, 以及该模式的优点以及缺点,.NET大家可以参看《大话设计模式》,java人员参考《设计模式之禅》。这里就不多谈了。这里主要谈一下该模式在自己做过的几个项目中是怎样运用的。
1.1 业务场景:
由于项目需要,本人需要开发一个CS架构的服务器端程序,完成的功能是实现前端传感器的数据的采集入库。通讯方式是采用串口通讯,实现ms级数据的刷新。那好问题来了。
第一:虽然都是串口通讯,但是前端传感器种类不一,存在多种传感器。熟悉硬件的都知道,不同类别的传感器串口协议不一样,导致解析肯定不一样。以下是一个典型串口协议字段定义图。
同步 (SYNC) |
起始 (STX) |
数据长度 (LEN) |
指令 (CMD) |
数据体 (DATA) |
校验 (CRC) |
1byte |
1byte |
1byte |
1byte |
最大 252byte |
2byte |
|
|
数据长度(LEN) |
第二:系统还需考虑后续新类型的传感器接入,所以对于系统扩展性要求较高
第三:要实现ms级的数据刷新。
1.2整体架构
服务器端:以windowsservice的形式注册到系统中,实现一个后台对于前端传感器数据采集、解析、入库的功能。
客户端:采用remoting技术,从服务器端获取传感器信息,在客户端上显示。
1.3命令模式的应用实例
1.3.1 串口通讯类
既然是要从串口读取数据,当然要有串口通信类了哦。以下是串口通讯类
这个类没什么好说的,主要就是一些串口的操作,值得一提的是事件onReceiveMessage。这个事件的调用是在串口收到数据的时候调用。该部分的初始化工作如下:
stringCMportNumber =ApplicationProperties.CMportNumber;
intCMdataBits =ApplicationProperties.CMdataBits;
int CMbaudRate=ApplicationProperties.CMbaudRate;
intCMpacketSize =ApplicationProperties.CMpacketSize;
serialPort0 = newCommSerialPort(CMportNumber, CMbaudRate,CMdataBits,CMpacketSize);
ITerminalcmSensorTerminal =newCMSensorTerminal();
serialPort0.onReceiveMessage += new CommSerialPort.onMessageHandle(cmSensorTerminal.ReviveData);
CMSensorTerminal即是我们的众多传感器中的一种,暂且叫做CM传感器
1.3.2双队列缓存方式实现数据上传入库
以上则完成了串口类的初始化工作,并且将传感器CM的监听事件(ReceiveData)注册上去,只要该串口有数据传入,及调用该CMSensorTerminal类得ReceiveData函数,进行解析,可能有人会说既然都接送到了数据,那我们直接在receiveData函数里面直接解析数据,然后Insert到数据库不就行了吗?这样是不行的,现实中我们传感器采集要求是ms级的,直接采用进行ADO的操作是不行的,联想到操作系统的消息队列,那好,我们采用双队列的模式,进行处理。这里我们引入DataHandlerThread类,
这个后台线程类维护了2个队列,一个是receiveCommand队列,另外一个是processCommand队列,这个类的功能即是不断的将receiveCommand队列的命令,推送到processCommand队列中去(注意加锁),这里的命令即是我们不同类传感器数据的插入命令。这个类扮演的即是一个缓存的角色,将大量的传感器入库命令先传入receiveCommand队列,然后在推入processCommand队列。
第二个类是dataprocessThread类
这个类得主要功能,即是将processCommand队列中的命令提取出来,进行入库处理。真实的入库处理是在这个类里面直接处理的吗?不是,而是采用命令模式,这个类扮演的是一个invoker的角色,每类传感器都是在其对应的dataprovider里面进行处理的。例如CMCommand传感器,对应于CMSensorProvider(对应命令模式里面的receiver)
总结:再看最后我们解决了三个业务问题了吗?
1. 新类型传感器的扩展
只需继承TerminalCommand,编写该传感器的dataprovider类,在该类里面进行DAO操作即可,做到对修改封闭,对扩展开放的原则。无所修改现有代码
2. 毫秒级刷新
在datahandler类里面,只需创建多个dataprocessThread类,开启多个线程对于同一命令队列进行处理即可。
3. 不同串口协议的解析
由于命令模式对命令的扩展很方便,所以上述问题完成可以在Command中得到解决
Ps:可能有人说串口太简单了吧?搞这么麻烦,大家仔细注意下,主要把串口类改成socket操作类,我们就可以实现了基于socket操作的服务器端了,后续command,receiver,invoker(双队列处理)架构完全可以不变。实际项目中可正是这样做的。
代码太多,就没有贴了,稍后给出github地址,上传的都是实际商业项目中的干货,给大家分享 ---Predator.Zhang