写之前说几句话。
用C语言写代码的同学看到“设计模式”不要敬而远之,借“设计模式是面向对象的语言才需要关心的东西”之名,行偷懒之实。学习设计模式,学的是设计模式里的设计思想,学习它以怎样的角度切入来实现低耦合。
C常用设计模式系列,我会以C语言常用的概念来描述这些设计模式。
本篇讲策略模式。
如图所示,C代码是过程式的,可以用“框架流程”一以贯之的,在某一个处理流程的结点处,有一个策略A代码。某一天来了一个需求,说想增加一种策略B,通过开关来控制。程序员小宋江说so easy,写了代码如下:
...
if (strategy == A)
{
...//一堆策略A处理代码
}
else
{
...//一堆策略B处理代码
}
...
程序员小李逵说:“哥哥啊,你这代码很难懂啊,我看完策略A的处理,忽然看到else,我忘了跟else并列的if是干啥的了”。
小宋江脸一红说,“你个憨货,你懂啥,来来看我改改”
...
if (strategy == A)
{
ProcStrategyA();
}
else
{
ProcStaregyB();
}
...
小李逵一看,“哟哥哥欸,这个不错,做了函数封装逻辑真清晰,连注释都省了”。
小宋江很受用。这个时候新员工小史进过来了,小史进一脸愁容,“二位哥哥,我们的这代码真的太难懂了,刚刚听负责质量度量的小公孙胜同学说,咱们的条件分支已经1w个了,这么复杂我们新员工学得很痛苦呀,二位哥哥我们有没有办法别在核心业务逻辑里加这么多的if判断?”小宋江摇了摇头。小李逵想,作为在团队干了很多年的小宝玉一定知道,这一问,没想到小宝玉说出这么一句话来,“研究那劳什子玩意做什么,也没见隔壁如花似玉的小林姑娘研究这玩意”。吐槽归吐槽,小宝玉还是认真地讲起来,“分析题目,这个软件是有两个相对独立的过程的,一个是这个开关配置过程,一个是软件一般运行过程,我们一般把后者叫核心模块。不想在核心模块增加圈复杂度也简单,那就把if判断放在开关配置过程里处理嘛,来来,这位哥哥把笔拿来”。
//开关配置过程
typedef void (*ProcStrategy)();
ProcStrategy procStrategy;
if (strategy == A)
{
procStrategy = ProcStrategyA;
}
else
{
procStrategy = ProcStaregyB;
}
//核心逻辑处理过程
...
procStrategy();
...
小宝玉道:“喏你看,核心处理里没有增加if了吧?”
小李逵道:“神了,那个 procStrategy 怎么还能当函数用?”。一边的小宋江忍不住了,“函数名不就是个地址嘛,把这个地址存下来的变量当然能当函数用了!”。小李逵似懂非懂不敢吭声了,但小史进问了,“这这这,这么复杂!我在我们代码里见过这样的,这都不能用F3一直看代码,还得过一会儿就找一下变量赋值的地方,难看死了”,小宝玉接话:“就是,隔壁小薛妹妹也不这么写代码,我们要这劳什子做什么。”坐在旁边办公的小贾政终于发话了:“你怎么老拿小林姑娘和小薛妹妹的代码跟小宋他们比,小宋他们条件分支有1w,小林他们才300,你要只有30个条件分支你连函数都不用封装我都能记住所有逻辑!还有小史进,看代码不方便,那要看你怎么看了,你要专注于框架流程,你就不要来回跳转,你要专注于两种策略具体的内容,那你就看两处具体的处理函数就行了,这叫关注点分离懂不懂,设计模式里这叫策略模式”。
小李逵:“奥,这就是策略模式啊”!
隔壁的小诸葛亮说,这个跟《设计模式》里说的不一样啊。确实不一样,也没必要一样,C语言有自己的思维模式,我们学习到其中怎样分离了关注点也就可以了,实现成与C++一模一样的形式也就落了无意义的炫技的窠臼。
一些概念我们统一一下:我们把 procStrategy 叫做接口,把 ProcStrategyA 叫做实现。
总结一下策略模式的要点。在软件配置阶段,根据开关状态的不同,我们给一个接口赋一个具体实现,在软件运行阶段,仅调用接口。