我有一个梦想,我写的代码,可以像诗一样优美。我有一个梦想,我做的设计,能恰到好处,既不过度,也无不足。
前面说了常用的工厂方法模式、简单工厂、抽象工厂,今天说一下策略模式。
策略模式我的理解其实很像工厂模式,这也是为什么第二个会写策略模式的原因。
策略模式
从结构上来讲策略模式分为一个持有具体算法引用的类(下文为了方便描述,统称为上下文使用类)和算法,外部调用者通过传入不同的条件,由上下文使用类生出不同的算法,根据算法计算出外部想要的结果,返回给调用者。
为什么要用策略模式
当业务规则在不同时期会有不同的变化,
具体来分析一下
先来梳理一下策略模式中的各结构部分:
上下文使用类,算法基类(抽象类),各算法具体实现类(继承自算法基类)。
仔细看一下,是不是和工厂模式有些神似。
下面将通过实际代码来展示一下,一下代码是我项目中写的一个有关解析的策略模式使用部分(当然了,肯定不能是项目中直接拿出来)
需求
脱离实际情况的任何涉及都是扯淡,先简要说明一下这部分的需求。
需求:
通过对坐标文件的解析,获取一份关于台站名称和其对应的坐标位置的map.
项目启动时,给到的坐标文件只有一种格式(这也是埋下了要使用策略的伏笔),即“台站名称 — 南北坐标 — 东西坐标 ---- 高层坐标 ”,为了后文描述,统称为“名称 ---- X坐标 ---- Y坐标 ---- Z坐标 ”,Z坐标如果没有则默认为0。
好,有了需求,实现就简单了,反正确定了只有一种格式。
class TxtParser
{
public:
TxtParser();
~TxtParser();
public:
int ParserFile(QString filename);
QMap<QString, Position> getMapData();
private:
QMap<QString, Position> m_position;
QFile fp_;
};
简单写一下类的主要结构,其他私有函数以及实现就不写了,就是解析文本文件相关。
在项目中需要解析的时候,直接使用TxtParser解析获取QMap<QString, Position>即可。
注:Position为自定义的结构
struct Position
{
float m_x;
float m_y;
float m_z;
};
项目1.0交给客户的时候,开始还好好的,后面就反馈坐标文件解析不成功,所有台站坐标都是(0, 0, 0)。
这种(0, 0, 0)的坐标产生,只能说明文件解析出错,当某个台站的坐标不正确,取得的字符串没有成功转化为数字时,软件将缺省坐标为(0, 0, 0)。
把日志发过来一看,果然是嘛,于是让客户检查坐标文件,答复:每个坐标都正确。
于是乎,自己达到客户使用的坐标文件,一看,好嘛,坐标是都没问题,可是文件格式都变了好嘛。
新的文件格式为:
“台阵名称 ----- 数据文件名称 ---- X ----- Y ----- Z ”
这不解析失败才怪。
为什么会有个数据文件名称,涉及项目结构,解释起来有点麻烦,就不说了,反正知道格式有了变化就行了。
这不需求就来了,再添加一种格式文件的解析。
先谈一下解决思路:
条条大路通罗马,但是总有一条更适合走。
两种类型,首先能想到的是在原TxtParser类中添加针对这种格式是成员函数,比如:
class TxtParser
{
public:
TxtParser();
~TxtParser();
public:
int ParserFile(QString filename);
int ParserFile(QString filename, int type);
QMap<QString, Position> getMapData();
private:
QMap<QString, Position> m_position;
QFile fp_;
};
这种方法通过函数重载方式,可以达到我们想要的效果,但是总有一点不舒服,如果需求刚开始就非常明确就是两种格式,以后也不会改变和增加,那么开始就设计成这种函数重载方式完全ok。
但是这种需求似乎并不能够这么明确的定死,这次突然加入一种格式就是个例子,如果每次坐标文件的格式有所变化,那是不是每次都要更改这个解析类?
答案是:不是(或者最好不是)。
大到整个项目的设计,小到一个小模块的设计都应该遵循一个道理:开放–封闭原则。
即当产生变化时,结构或者类对扩展开放,对更改关闭。
也就是说当你要增添一个功能的时候,最好的方式应该是在原有的基础上去扩展,而不应该选择去改变已经有的部分,因为如果一个项目或者模块足够复杂,这意味着你可能不会注意到其他地方的使用情况,也就有可能造成现有系统或者模块的崩溃等问题。
好,现在确定不用这条路子,那换一种路子,重写一个解析类行吗?
class TxtParser2
{
public:
TxtParser2();
~TxtParser2();
public:
int ParserFile(QString filename);
QMap<QString, Position> getMapData();
private:
QMap<QString, Position> m_position;
QFile fp_;
};
可以,完全ok,结合上篇说到的工厂,这里完全可以套用。
一、客户端(也就是使用解析类的模块)在不同的时刻自己选择实例化相应的解析类,进而完成解析等运算。
PS:这里的解析类就作为产品类,客户端自己相当于维持了一个工厂的作用,选择生产的产品。
注意
这里就要要求使用者必须明确知道有哪些解析类,并且知道什么时候使用什么类,当相似的类变得越来越多的时候,对客户端的使用压力会增大。
二、使用工厂方法模式
每一个工厂对应一种解析类(产品)。
注意
这种方法要做的有:每增加一种解析类,则对应增加一个工厂类,代码量上会增加(对程序员来说,这就是原罪),而且使用端使用过程中至少要了解的:1.所有的工厂类以及什么时候使用什么工厂,2.至少要知道一个产品的抽象基类(如果采取所有解析类继承自同一个抽象基类的情况),如果不使用抽象基类,那不好意思,使用端还需要知道所有的解析类(产品类),还要知道哪种解析类对应哪种工厂。
光是想到这些,我想大家也不会愿意吧。
三、抽象工厂
我觉得这个需求没有必要硬往上面靠,就不强求贴着说了。
下面直接来说说我的解决方法 --------策略模式(其实是策略模式加简单工厂的组合)
先上代码:
class ConfigFileText
{
public:
ConfigFileText();
~ConfigFileText();
bool setFile(QString file);
QMap<QString, Position> getNamePosMap();
private:
IParser *m_iparser;
};
class IParser //抽象基类,后面的解析类都继承自此类
{
public:
//IParser();
virtual ~IParser();
virtual bool parserFile(QString fileName) = 0;
virtual QMap<QString, Position> getNamePosMap() = 0;
public:
QFile m_fp;
};
class TxtParser1 : public IParser
{
public:
singleParser();
~singleParser();
virtual bool parserFile(QStringList fileName);
virtual QMap<QString, Position> getNamePosMap();
private:
QMap<QString, Position> m_name_pos;
};
class TxtParser2 : public IParser
{
public:
singleParser();
~singleParser();
virtual bool parserFile(QStringList fileName);
virtual QMap<QString, Position> getNamePosMap();
private:
QMap<QString, Position> m_name_pos;
};
为了方便了解使用原理,我把其他的实际项目相关部分全部去掉了,然后将其中的单个文件部分拆开形成两个解析类来讲,现在我们来讲一下各部分的关联以及使用的过程。
TxtParser1、 Txtparser2都继承自IParser基类,ConfigFileText类内部维持了一个对解析类的引用,即IParser *指针。
使用:
客户端只需要知道ConfigFileText这个类,通过setFile()将文件完整名称传入ConfigFileText,然后通过getNamePosMap()获取QMap即可。在setFile()中由ConfigFileText处理选择实例化哪个解析类。
整个过程中使用端只需要知道ConfigFileText类即可,不需要也不必需要知道有哪几种解析类,只管传入完整文件名及获取数据即可。
到了这里,相信仔细的小伙伴已经看出来不是单纯的策略类了吧,单纯的策略类,还是需要外部使用端明确使用哪种策略(或者算法),但是这样外部使用端就又需要了解哪种情况下使用哪种策略,使用压力就变大了。
用简单工厂方法和策略类的结合,好处在于将判断实例化策略的压力转嫁到ConfigFileText类中,使用端不需要了解内部的实现,当然也有缺点:就是在增加新的策略(这里指的是解析方式)时,在扩展一个解析类的同时,还需要在ConfigFileText的内部工厂中增加对应解析类的判断以及实例化,这样就又违背了“封闭—开放原则”。
结论:
无论哪一种模式都有其自己的优点和缺点,对于选择哪一种方式,还需要自己在实际情况下去判断
写在最后:
本来想把策略模式从其原本的由客户端判断使用哪种策略的方式开始写,但是,码字是真的累,都没有自动补全功能(不习惯,哈哈),这些都是分好几次写才写完。
另外:代码地址(和本文不相同相同,本文讲解了单个文件的内容,代码中考虑了以后可能增加多文件解析的情况,将涉及具体实现的部分都作了删除,只保留几个功能演示函数)。
https://download.csdn.net/download/ta_123123/12446832