遵循开闭原则设计出的模块具有两个主要特征:
(1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
(2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
注意:这里的修改指的是因功能拓展而引起的修改!!!!
实现方法:抽象出所有变化的部分。
EVP要求在做系统设计的时候,对系统所有可能发生的变化部分进行评估和分类,每一个可变的因素都单独进行封装。
举例分析:
小狗在不同状态(心情)下,会有不同的移动方式(狗也是有情绪滴撒)。比如:在开心的时候,跳着跑;伤心的时候低着头跑;害怕的时候,夹着尾巴跑。
版本一:
if(state == "开心")
跳着跑;
else if(state == "伤心")
低着头跑
else if(state == "害怕")
夹着尾巴跑;
else
正常跑
假如这段代码是作为sdk发出去的,不允许对其随便修改!然而现在有新需求,要求在愤怒的时候,咬牙跑。问题来了,打破了开闭原则!
版本二:
IMove __move
void setMoveStage(IMove move)
{
__move = move
}
void move()
{
__move()
}
client:
if(dog.state == "开心")
dog.setMoveStage(跳着跑)
else if(dog.state == "伤心")
dog.setMoveStage(低着头跑)
else if(dog.state == "害怕")
dog.setMoveStage(夹着尾巴跑;)
else if(dog.state == "愤怒")
dog.setMoveStage(咬牙跑)
else
dog.setMoveStage(正常跑)
将变化的部分抽象出来,不变的部分放在dog中,现在再进行拓展,就不用修改dog的类了,只需要修改客户端。
版本二的方法已经解决了开闭原则问题,并且它就是一个简单的策略模式,但这样的代码太丑了,如果你有强迫症,可以再进行修改
版本三
void registMove(State state, IMove move)
{
dictionary[state] = move;
}
void setState(State state)
{
mState = state;
mMove = dictionary[state];
}
client:
dog.setState("开心")
这样,只需要在开始时,注册好所有的移动方式,然后在每次改变心情时,自动的修改了相应的move方式。如果要进行拓展,添加新的移动方式,也只需要修改客户端启动时,注册move的地方。
下面是个计算器的例子:
以简单的计算器程序来说。假如让你设计一个支持+、-、*、%的计算器,那么在设计的时候肯定会想到以后会不会有拓展?拓展到支持平方、幂运算、开方等运算?那么,这里的运算功能就是一个可变部分,所以要对这个可变部分进行抽象。如图:
当要拓展新的运算时,只需要根据操作数的个数添加相应的实体类就行了。