转载连接: http://blog.youkuaiyun.com/ithzhang/article/details/62894635
模板方法模式在协议构造与解析中的应用
实际开发中经常会遇到构造、解析各种二进制、Xml、json协议等。很容易想到的方案就是每个协议提供一个构造、解析的方法。这种方案好处是简单,任何人都召之即来。缺点也很明显,每个协议单独构造。如果需要修改这些协议里共有的每个字段,比如加个errInfo,则需要修改所有的地方。
这种方案使用的是最简单的面向过程的思想。要知道我们在开发的是面向对象的代码。为何不能充分利用面向对象的一些思想、原则、设计模式构造出灵活、可扩展的代码。而不是使用面向对象的语言、喊着面向对象的口号,写着C一样的面向过程的代码。
面向对象中的设计模式是前人依据面向对象原则总结出来的解决现实问题的已经被认可的现成的解决方案。若能将他们运用到我们的开发中必会起到事半功倍的效果。今天我们主要介绍如何应用模板方法模式来解决经常遇到的协议构造和解析问题。由于面向对象思想和设计模式比较抽象,这次介绍将采用理论结合实例的方式,后面还会有实现代码。该方案已经在xx(公司项目)开发中广泛使用,希望更多的同事能够体会到它的魅力所在,并在开发工作中应用并感受它带来的便利性。
什么是模板方法模式?
定义一个操作中的算法的骨架,而将步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
优点:
1. 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
2. 子类实现算法的某些细节,有助于算法的扩展。
3. 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
缺点:
每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
适用场景:
1. 在某些类的算法中,用了相同的方法,造成代码的重复。
2. 控制子类扩展,子类必须遵守算法规则。
上面的定义和类图比较抽象。下面我们来解释一下。
之所以叫做模板方法模式,就是因为在基类中已经定义了一个骨架。子类按照基类定义的骨架去实现即可。这个骨架就类似于模板,子类就是这个模板的具体实现。好像还比较抽象,没有办法,设计模式这个东西一直很难理解。就因为难以理解,所以会用的人不多。
闲言碎语不要讲,直接开始正题。
我们在开发一个新的软件时,如网络通信时需要构造各种协议。如二进制、json、xml协议。每种操作对应一个协议,所以一般会定义几十甚至是上百个协议。比如在开发插件进程化、restapi时均定义了数十套协议。这么多的协议如果我们每个都要从头开始构造,也真够让人头疼的。经过研究发现这里比较适合使用模板方法模式。
首先一套协议一般具有相似的格式。如在restapi中的请求协议:
我们可以定义为所有的操作请求都类似上面的格式。不同的操作只是params字段不同而已。对于这种几十个相似的协议,每个单独去定义也会导致大量的重复代码。重复代码的缺点想必大家都很清楚,大家实现的时候也是不停的copy、paste。这时代码中的某些缺陷就会通过这个操作扩散。需要添加字段时也需要一个一个拿来修改。通过模板方法模式,可以避免重复代码导致的以上问题。基类实现的代码,一般经过多次测试,一般具备很高的鲁棒性。
往模板方法模式上套,我们可以这样理解:所有协议都包括command和params节点。它们之间只是params内部的参数不同而已。
我们可以在基类定义构造command和params的骨架,params的具体实现交由子类去做。
类图如下:
CRequest为纯虚的接口类。定义了两个public的接口:Serialize()用于序列化,即组装协议。Deserialzie()用于反序列图,即解析协议。
CPTZBaseRequest继承自CRequest,并定义了纯虚的protected的GetParams和ParseParams()方法。同时定义了m_command成员变量。
CptzDirectionCtrlRequest、CPtzLockRequest、CPtzSetZoomInRequest为具体的子类(当然不止这三个,restful协议目前已经有30多套协议,后面还会扩充),每个子类的区别在各自的GetParams和ParseParams实现。
后面为相关代码,正在实际项目使用。Json构造和解析使用jsoncpp(json开源库)。
- CPtzBaseRequest的Serialize()和Deserialize()定义了算法的骨架。
- std::string CPtzBaseRequest::Serialize()//协议构造骨架
- {
- Json::Value root;
- root["command"]= Json::Value(m_strCmd); //构造协议的公有部分
- root["params"]= GetParam();//构造协议的私有部分,交由各子类去实现
- //以上两步就是整个协议体系的骨架,协议构造和
- Json::FastWriterwriter;
- return writer.write(root);
- }
- bool CPtzBaseRequest::Deserialize(const string&str )//协议解析骨架
- {
- if(str.empty())
- {
- return false;
- }
- Json::Reader reader;
- Json::Value root;
- if (!reader.parse(str, root, false))
- {
- return false;
- }
- try
- {
- m_strCmd = root["command"].asString();//构造协议的公有部分
- Json::Value param = root["params"];//解析协议的私有部分,交由各子类去实现
- return ParseParams(param);
- }
- catch(...)
- {
- return false;
- }
- }
GetParams和ParseParams()为纯虚方法,需要在各自的子类中实现。
CPtzDirectionCtrlRequest的实现:
- bool CPtzDirectionCtrlRequest::ParseParams(const Json::Value& value)//子类解析自己特有的部分
- {
- if(value.isNull())
- {
- return false;
- }
- m_Command = value["command"].asString();
- m_priority = value["priority"].asInt();
- m_userId = value["userId"].asString();
- m_action = value["action"].asString();
- m_speed = value["speed"].asInt();
- m_remark = value["remark"].asString();
- return true;
- }
- Json::Value CPtzDirectionCtrlRequest::GetParam()//子类构造自己特有的部分
- {
- Json::Value value;
- value["command"]= Json::Value(m_Command);
- value["priority"]= Json::Value(m_priority);
- value["userId"]= Json::Value(m_userId);
- value["action"]= Json::Value(m_action);
- value["speed"]= Json::Value(m_speed);
- value["remark"]= Json::Value(m_remark);
- return value;
- }
CPtzLockRequest的实现:
- bool CPtzLockRequest::ParseParams(const Json::Value& root) //子类解析自己特有的部分
- {
- if (root.isNull())
- {
- return false;
- }
- m_priority = root["priority"].asInt();
- m_userId = root["userId"].asString();
- m_action = root["action"].asString();
- m_remark = root["remark"].asString();
- m_lockTime = root["lockTime"].asInt();
- return true;
- }
- Json::Value CPtzLockRequest::GetParam()//子类构造自己特有的部分
- {
- Json::Value value;
- value["priority"]= Json::Value(m_priority);
- value["userId"]= Json::Value(m_userId);
- value["action"]= Json::Value(m_action);
- value["remark"]= Json::Value(m_remark);
- value["lockTime"]= Json::Value(m_lockTime);
- return value;
- }
CPtzSelZoomInRequest的实现
- bool CPtzSelZoomInRequest::ParseParams(const Json::Value& value) //子类解析自己特有的部分
- {
- if(value.isNull())
- {
- return false;
- }
- m_priority = value["priority"].asInt();
- m_userId = value["userId"].asString();
- m_startX = value["startX"].asInt();
- m_startY = value["startY"].asInt();
- m_endX = value["endX"].asInt();
- m_endY = value["endY"].asInt();
- m_remark = value["remark"].asString();
- return true;
- }
- Json::Value CPtzSelZoomInRequest::GetParam()//子类构造自己特有的部分
- {
- Json::Value value;
- value["priority"]= Json::Value(m_priority);
- value["userId"]= Json::Value(m_userId);
- value["startX"]= Json::Value(m_startX);
- value["startY"]= Json::Value(m_startY);
- value["endX"]= Json::Value(m_endX);
- value["endY"]= Json::Value(m_endY);
- value["remark"]= Json::Value(m_remark);
- return value;
- }
可以看到,在应用了模板方法模式之后增加一个新的协议变得如此简单,只需要从基类继承一个子类,然后实现该子类特有的地方即可。
这个时候有些同学可能会提出一个问题。如果协议里除了command和params字段之外还有其他字段该如何处理呢?很简单,只需要重新构建一个新的继承体系!在基类中构造骨架处理公共部分,子类处理特有的部分。
当然模板方法模式也会有一个副作用,由于每一个协议都会对应一个子类。此方案可能会导致类数目过多,不好管理。这一点也不用担心,模板方法模式一般不单独使用,可以跟工厂模式、简单工厂模式组合使用。由工厂模式根据外部输入负责创建相应的协议类。
模板方法模式不仅能够避免重复代码,减少代码量。还有个很重要的原因。由于基类骨架代码一般都经过充分测试,后面只需要保证子类的协议构造和解析没有问题,就可以很轻松的构造出鲁棒性强的代码。这也是开放封闭原则:对扩展开放,对修改关闭最直接的体现吧!
本文介绍模板方法模式在协议构造与解析中的应用,通过实例展示如何避免重复代码,提高代码复用性和可维护性。
657

被折叠的 条评论
为什么被折叠?



