起步
策略模式属于行为型,常用来避免冗长的分支判断。
概念与使用都极为简单,这里就用维基百科中的一句话诠释:the strategy pattern enables selecting an algorithm at runtime。
初探策略模式
现在假设项目需要解析三种类型的配置文件,分别是 json 文件、xml 文件、yaml 文件。于是我们最快能想到的代码实现如下:
class ConfigSource(object):
def load(self, file):
_, ext = os.path.splitext(file)
if ext == ".json":
res = json_parse(file)
elif ext == ".xml":
res = xml_parse(file)
elif ext == ".yaml":
res = yaml_parse(file)
else:
raise NotImplementedError
return res
假设哪天我们需要解析 toml 格式的配置文件,就不得不去修改 load 方法:
def load(self, file):
_, ext = os.path.splitext(file)
...
elif ext == ".yaml":
res = yaml_parse(file)
elif ext == ".toml":
res = toml_parse(file)
...
依次类推,不但是违背了封闭原则,还使得 if ... else 分支冗长。而策略模式认为,不要直接去实现一种算法(这里可以认为是功能),而要实现一组算法,在运行时选择其中一种。
同时从设计原则来看,高层模块不要依赖底层模块,高层与底层共同依赖抽象。所以作为客户端的 load 方法,不应该直接去选择解析方法(json_parse, xml_parse...),而是要借助抽象模块与解析方法建立一个沟通的桥梁。先优化这组解析方法:
import abc
class ParseStrategy(metaclass=abc.ABCMeta):
@abc.abstractmethod
def parse(self, file):
pass
class JsonStrategy(ParseStrategy):
def parse(self, file):
return ...
class XmlStrategy(ParseStrategy):
def parse(self, file):
return ...
class YamlStrategy(ParseStrategy):
def parse(self, file):
return ...
客户端不应该、也没必要知道子系统的代码逻辑,那么如何选择策略这个难题我们可以拿之前说过的简单工厂来解决。
class StrategyFactory(object):
@classmethod
def create_strategy(cls, ext):
if ext == ".json":
strategy = JsonStrategy()
elif ext == ".xml":
strategy = XmlStrategy()
elif ext == ".yaml":
strategy = XmlStrategy()
else:
raise NotImplementedError
return strategy
class ConfigSource(object):
def load(self, file):
_, ext = os.path.splitext(file)
# 获取策略
strategy = StrategyFactory.create_strategy(ext)
# 解析配置
return strategy.parse(file)
你应该注意 StrategyFactory 和 ConfigSource 的不同。事实上,设计原则与设计模式并不为了消灭复杂,只能够转移复杂。if ... else 分支虽然还存在,但不属于客户端(接口使用者)的知识负担了。
再探策略模式
if ... else 分支虽然转嫁到接口开发者身上,但毕竟没有被除掉,不是说策略模式可以避免 if ... else 冗余吗?现在这样说不过去吧!在 Python 中,我们是可以借助 dict 或者 list 来根除判断分支的冗余的。
具体修改在工厂类中。只需要提前创建好策略,放到一个字典中即可:
class StrategyFactory(object):
strategys = {
".json": JsonStrategy(),
".xml": XmlStrategy(),
".yaml": YamlStrategy(),
}
@classmethod
def create_strategy(cls, ext):
strategy = cls.strategys.get(ext)
if strategy is None:
raise NotImplementedError
return strategy
基于列表的实现原理差不多,但需要遍历出每个元素,再判断是否是目标算法,如果是就取出来用,如果不是就继续遍历。当元素个数较少,或者内存资源紧张时,采用 list + 遍历 代替字典也 ok 的。
总结
不知道你有没有这种感觉,基于 Python 学习策略模式会很迷惑,觉得这模式卵用,感觉不到意义。我甚至不能在《Learning Python Design Patterns Second Edition》一书中找到策略模式。我以为这种的困惑是 Python 没有强类型约束导致的,比如 JsonStrategy 的实例 json_strategy 可以被赋值为 XmlStrategy 的实例,你感觉不到这里边的差别。但在 C++ 中区别就大了。
没有使用策略模式之前,你需要单独创建解析器对象,单独去调用 parse 方法。
class Config {};
class JsonParser {
public:
Config parse(string file) {
cout << "parse config file.json" << endl;
return Config();
}
};
class XmlParser {
public:
Config parse(string file) {
cout << "parse config file.xml" << endl;
return Config();
}
};
int main() {
string ext = ".xml";
if (ext == ".json") {
JsonParser *jsonParser = new JsonParser();
jsonParser->parse("xxx.json");
} else if (ext == ".xml") {
XmlParser *xmlParser = new XmlParser();
xmlParser->parse("xxx.xml");
}
// 错误操作, 不同类型之间的指针是不允许直接赋值的
// jsonParser = xmlParser
return 0;
}
策略模式之后:
class Config {};
class ParseStrategy { // 接口
public:
virtual Config parse(string file) = 0;
};
class JsonStrategy: public ParseStrategy {
public:
virtual Config parse(string file) {
cout << "parse config file.json" << endl;
return Config();
}
};
class XmlStrategy: public ParseStrategy {
public:
virtual Config parse(string file) {
cout << "parse config file.xml" << endl;
return Config();
}
};
int main() {
string ext = ".xml";
// 注意使用上的差异
ParseStrategy *strategy = NULL;
if (ext == ".json") {
strategy = new JsonStrategy();
} else if (ext == ".xml") {
strategy = new XmlStrategy();
}
strategy->parse("xxx.json");
return 0;
}
代码果断简洁,实际场景越复杂效果就会越明显。
还不快抢沙发