我的OO实践---由GPS消息处理抽象出一通用命令处理类
2010-11-02 09:39 Loning 阅读(2115) 评论(2) 编辑 收藏 举报
-
实验要求
(1)综合运用以前学到的控制语句、继承、封装、接口等知识,完成具有实际运用功能的程序。
(2)通过运用学过的知识进一步的巩固和掌握学到的知识。
-
实验内容
-
使用GPS GATE软件模拟GPS卫星发出的GPS信号,编写程序对GPS GATE发出的信息进行接收、解析、处理。将处理好的信息按照固定的格式存储至文件中(经度、纬度、时间、速度、高度)。
下面是主要用到的GPS信息的格式:
1. GPS/TRANSIT Data(RMC)推荐定位信息 (在项目中就使用了这个报文的定位数据)
$GPRMC,(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)*hh(CR)(LF)
(1)UTC时间,hhmmss(时分秒)格式
(2)定位状态,A=有效定位,V=无效定位
(3)纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
(4)纬度半球N(北半球)或S(南半球)
(5)经度dddmm.mmmm(度分)格式(前面的0也将被传输)
(6)经度半球E(东经)或W(西经)
(7)地面速率(000.0~999.9节,前面的0也将被传输)(8)地面航向(000.0~359.9度,以真北为参考基准,前面的0也将被传输) (9)UTC日期,ddmmyy(日月年)格式
(10)磁偏角(000.0~180.0度,前面的0也将被传输)
(11)磁偏角方向,E(东)或W(西)
(12)模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)(13) hh(CR)(LF) hh是前面从第一个数据到最后一个数据的校验和,(CR)(LF)是回车换行,表示一个字符串的结束。
具体示例:
$GPRMC,121212.456,A,3232.1234,N,12121.3322,W,0.15,305.12,121299, ,*22
2. GPS Fix Data(GGA)GPS定位信息
$GPGGA,(1),(2),(3),(4),(5),(6),(7),(8),(9),M,(10),M,(11),(12)*hh(CR)(LF)
(1)UTC时间,hhmmss(时分秒)格式
(2)纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
(3)纬度半球N(北半球)或S(南半球)
(4)经度dddmm.mmmm(度分)格式(前面的0也将被传输)
(5)经度半球E(东经)或W(西经)
(6)GPS状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
(7)正在使用解算位置的卫星数量(00~12)(前面的0也将被传输)
(8)HDOP水平精度因子(0.5~99.9)
(9)海拔高度(-9999.9~99999.9)
(10)地球椭球面相对大地水准面的高度
(11)差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
(12)差分站ID号0000~1023(前面的0也将被传输,如果不是差分定位将为空)(13) hh(CR)(LF) hh是前面从第一个数据到最后一个数据的校验和,(CR)(LF)是回车换行,表示一个字符串的结束。
具体示例:
-
$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F
校验和是指这一行的所有非数字字符,按照"字母、空格、句点、正号= 0;负号=1"的规则换算成0和1后,将这一行中原来的全部数字加起来,以10为模计算后所得的和。校验和可以检查出90%的数据存储或传送错误。按十进制加起来的个位数字的校验和,用于精确纠正误差。
-
使用异常处理,对接收信息的过程中可能产生的异常进行处理。
- (选作)编写C#Winfrom 程序,能够将解析到的数据轨迹绘制到相应的地图上。
-
详细设计
该实现主要是实践我在前文《谈谈我处理异常的一般方法》中提出的一些观点,以及一些面向对象设计的思想,和一些设计模式的运用。
在看到这个问题的时候,首先要对需求进行分析。该问题的数据流程比较清晰,见Figure 1。
Figure 1 数据流
从个人经验来讲,我认为在指令的解析部分可以抽象出一个比较通用的方式,我们把GPS指令文本流替换为二进制流,将单条指令文本替换为单条指令的二进制信息。
Figure 2 通用数据流
我做的抽象框架如所示Figure 3 类视图所示。
Figure 3 类视图
首先我们应当有一个类来处理整个问题,称之为CommandProcessor。它负责从外部设备接收数据、并且将处理后的消息通过事件的方式向外部提供。该类还应当能够将单条指令从流中取出来,因而我在这个类中提供了一个虚的方法GetSingleCommandString(),并且进行了一个通用的简单实现、即每行为一条指令。这里参数都用string来表示了,用string来表示二进制数据也没太大问题,但对于纯文本的数据处理起来则比较方便。
在将命令分出来之后,我们需要将一条单独的指令转换为一个强类型的命令。我们用ICommand来表示一个被分析后的命令,则问题就是如何由一个string产生一个ICommand。
根据面向对象设计的一些原则,数据与实现应都放在一个类当中。那么对于特殊的Command,它自身才知道如何对它对应的string进行分析。我们在ICommand中定义Parse(string s)方法对string进行分析。
ICommand的定义如下,关于DoCommand()方法在后文将有说明。
public interface ICommand { /// <summary> /// If it is right command,return true /// </summary> /// <param name="str"></param> /// <returns></returns> bool Parse(string str); void DoCommand(); }
那么如何找到string->ICommand的对应关系呢?
比较简单的实现是我们维护一个ICommand的列表,然后用穷举的方式调用Parse方法,直到该方法返回true。如果没有方法返回true,则表示该string是一个未知的命令。这样实现是比较OO,在编程阶段添加ICommand比较方便。缺点是效率比较低,而且还需要维护一个已经实例化ICommand的列表。而有这样一个列表,在多线程编程时就会产生一些同步问题。在编程时需要在Parse(string s)方法中先对传入的string做一个初步检测,如果非该指令则立即返回false。而在当前的设计中,没法对子类重写Parse方法内的内容进行约束,如果实现不好则会更大的造成效率的降低。
我现在的实现是,引入一个ICommandFactory对象,那么这个问题就成为了string->ICommandFactory->ICommand的问题了,由于实现接口ICommandFactory与ICommand都是同一开发者实现的,该开发者可以分析string的特殊性,在实现ICommandFactory接口的类中找到string->ICommand的关系。还有一个优点是,我们完全可以在自己定义的CommandFactory中实现在上一段提到的方法。以下是该部分具体的实现代码。
public interface ICommandFactory { /// <summary> /// return an ICommand object from a string. /// </summary> /// <param name="str"></param> /// <returns></returns> ICommand Parse(string str); }
//实现该接口的一个具体示例
public class GPSCommandFactory : ICommandFactory { public IUnityContainer UnityContainer { get; set; } private ICommand Parse<C>(string str) where C : ICommand { var cmd = UnityContainer.Resolve<C>(); if (!cmd.Parse(str)) { throw new InvalidCommandStringException(str, null); } return cmd; } #region ICommandFactory Members public ICommand Parse(string str) { try { string command; command = str.Substring(0, str.IndexOf(',')); switch (command) { case "$GPRMC": return Parse<GPRMCommand>(str); case "$GPGGA": return Parse<GPGGACommmand>(str); } throw new InvalidCommandStringException(str, null); } catch (ArgumentOutOfRangeException ex) { … } } #endregion }
在解决完命令分析的问题之后,最后的一个问题是如何将分析出的命令应用的问题。在这个设计中,我通过事件来通知其他类。
在前文给出ICommand的描述中给出了一个DoCommand()方法。我们可以在需要应用的项目中重写该方法,做出相应的动作。这样就完全的实现了多态,在接收到CommandReceived事件的消息后直接调用DoCommand()方法就好了,不需要对Command类型进行显示分析。当然,我们需要借助IOC容器来进行这样的实现。这里不阐述IOC容器的具体功能,有兴趣Google下就好啦。当然这样实现也许还是有一些复杂性的,我们需要对每一个Command类进行重写,然后更改IOC容器的映射关系。
还有一种实现是使用is操作,对ICommand对象进行测试,然后将ICommand中的信息进行利用。下面给出第二种实现方法的一个示例。
void CommandProcessor_CommandRecevied(object sender, CommandEventArgs e) { Invoke(new ThreadStart(delegate() { if (e.Command is GPGGACommmand) { ProcessCommand((GPGGACommmand)e.Command); } if (e.Command is GPRMCommand) { ProcessCommand((GPRMCommand)e.Command); } })); //throw new NotImplementedException(); }
这样一个基本的命令分析器的Library工程就基本写完了。然后就应当着手解决GPS消息的问题了。在前面的示例中已经贴了一些实现。
分析GPS的消息,很容易的发现每条命令是以$\.+?, 方式开始的,我们可以根据这样的特性实现ICommandFactory,这里再贴一下主要的实现代码
string command; command = str.Substring(0, str.IndexOf(',')); switch (command) { case "$GPRMC": return Parse<GPRMCommand>(str); case "$GPGGA": return Parse<GPGGACommmand>(str); }
然后就可以产生Command了。我们根据要求建立了两个Command,分别是GPRMCommand和GPGGACommmand。然后根据对应的命令格式重写Parse()方法。而消息分割正好是每行一条命令的方式,因而就无需重写CommandProcessor的GetSingleCommand方法了。然后就完了,不需要写什么了,基本的东西都已经在我们之前谈到的项目中定义好了。
具体视图如下:
Figure 4 GPS类视图
如果您有兴趣,可以在这里下载代码,如果有问题欢迎与我联系:)
http://loningproject.googlecode.com/svn/trunk/cnblogs/gps.7z