使用多态来做到open-close

自从看了设计模式了解到open-close原则后, 我在写代码时都是尽量遵循着open-close原则来进行编码。

而面向对象中的多态在做到open-close原则中起到不可忽略的作用。

一般在设计之初会先抽象出一个interface(也就是C++中的纯虚类), 这个interface中的函数接口一定是要仔细考量的,因为这关系到所有子类的实现。

然后根据具体情况去继承并实现interface,当我们新增功能时,仅仅重新继承一下interface生成一个新的类即可, 在一般情况下并不需要动到之前运行良好的类。当然不论你interface定义的有多么好, 在最后新增需求时都可能不会完全满足,然而这种情况下一般只需对interface增加函数即可, 对之前运行良好的代码也并不需要做出大的修改。

举个例子:
一个游戏有三个模式ModeA, ModeB, ModeC。我觉得这是一种最天然的抽象了,几乎就不用思考就可以肯定,多态一定是优于if else方式的。


使用多态的方式, 首先抽象出一个interface类似如下:

class IMode {
virtual doSomeThingA() = 0;
virtual doSomeThingB() = 0;
...
};

然后根据ModeA/ModeB/ModeC的实际玩法去实现IMode类开中函数。

这样只需要在游戏切换模式时使用如下类似代码散转一下即可:

IMode *mode;
switch (game_mode) {
case ModeA:
mode = new ModeA();
break;
...
default:
assert(!"unkonwn game mode");
break;
}

在代码主体逻辑中,仅仅使用mode指针即可,即主体逻辑并不关心当前mode是ModeA/ModeB/Modec中的哪个, 他仅仅去调用相关的IMode中的函数即可。

这样当去看主体逻辑代码时,暴露出来几乎全部是主体逻辑,而不会被模式相关的东西弄晕了头脑。

当新加一个ModeD模式时, 仅需要修改上面的Switch语句加一个case另外再实现一个class ModeD类即可, 并不需要修改之前运行良好的主体代码。也就符合了open-close原则。


再看if else的实现,假设如上IMode函数有50个函数DoSomething1~50, 如果使用if else的方式实现,则需要在调用DoSomething1~50的地方全写上类似如上switch的语句,需要搞清楚的是IMode有50个函数并不意味着这50个函数仅仅调用50次(甚至说是150次都不算多), 那么就会出现代码量暴增。

而且这样会导至所有的模式代码是以函数分开的,在增加每一个模式时主体代码都要增加50个函数并修改150处switch-case语句,如果改漏一处就会出现难以预料的bug, 这样大大增加了代码的维护难度。

当然这种还算是好的情况,如果在IMode类中的函数非常短,那么在使用if else方式实现时有些人偷懒就可能会直接用case一段代码语句直接搞定,甚至在后续开发中都可能出现这样的代码:

switch (game_mode) {
case ModeA:
case ModeB:
do_something();
if (game_mode == ModeA)
do_sometingA();
else
do_somethingB();
break;
...
};

如果有4个模式这样纠缠到一块,我打赌你看到这段代码时一定会想知道他家在哪里。

这样修改还有一个坏处就是并不符合open-close原则,因为你在改ModeB时可能会把ModeA改坏,而且有时后会很难测出来。


通过比较很容易就可以看出,多态和open-close的好处,在这里我并不想听到if-else/switch-case比使用多态效率高这种鬼话,我认为损失一点点性能能获得这么清晰的代码结构是非常值得的。

在《Unix编程艺术》P14中说过这样一句话,计算机编程的本质就是控制复杂度,比较两种实现方式可以看出第一种方式,不论增加多了种模式其复杂度几乎都不会增加,而第二种方式每增加一种模式都会使得代码变得更糟糕一些,而且永不停止直到他再也无法被维护。

发表评论

six × = sixty