现在的位置: 首页 > 综合 > 正文

开放接口、隐藏细节——由模式谈面向对象的基本原则之开闭原则

2013年06月04日 ⁄ 综合 ⁄ 共 2709字 ⁄ 字号 评论关闭
                                                               开放接口、隐藏细节
                                                      ——由模式谈面向对象的基本原则之开闭原则
 
        我们知道,对于我们编写完成的代码,要经历过多重测试。比如要经过开发人员的单元测试、集成测试,然后再到测试人员的白盒测试、黑盒测试,最后还要由用户进行一定的测试。等等,经过漫长的测试,代码才能投入使用。但是软件产品的维护和升级又是一个永恒的话题,在维护的过程中,你可能要不断的增加一些小功能;在升级的过程中,你要增加一些较大的功能。
也就是说,软件产品随时都有扩展功能的要求。这种功能的扩展,就要求我们改变原有的代码。比如,我们可以修改原有的代码,使之功能得到扩展。但是,对原代码的修改就会深刻的影响到原来的功能的方方面面:它可能对旧代码引入了新的错误,使你不得不对旧代码进行大规模的修改;更甚于它可能引起你不得不重新构造系统的架构;而所有这些,即使你新增的代码对旧代码没有影响,你也不得不对原来的系统做一个全面的测试。
所有上述列出来的问题,都是对系统功能进行扩展所不能承受的代价。换句话说,我们设计出来的系统,一定要是扩展性良好的系统。怎么样才能设计出扩展性良好的系统?开闭原则告诉我们:
软件系统必须对更改封闭;而对扩展开放。
换句话说,我们的系统是可扩展的系统,而不是可修改的系统。
依赖颠倒原则正是为了满足开闭原则而提出来的一个方案:对接口编程。只有依赖抽象,我们才好对系统扩展,也就是扩展系统的时候实现我们实现定义好的接口,而不是产生新的类
如,我们需要使用一个类,我们的代码会这么写:
Dog dog = new Dog();
……
一看这样的代码就是扩展性不好,因为如果需要的变化要我们增加一个新类:Cat的话,我们必须修改上面的代码如下:
//首先判断要生成什么对象
if(animal.equals(“Dog”))
{

        Dog dog = new Dog();

        ……
}
else if(animal.equals(“Cat”))
{

        Cat cat = new Cat();

        ……
}
这还不够,如果我们的新的动物类不断的增加,那么我们就需要不断的修改上面的代码,这显然是相当糟糕的代码。不符合我们的要求,我们想要的代码应该是这样的:

Animal animal = Factory.getInstance(animalName);

这种想法很简单,我们的客户端依赖的是一个Animal接口,而具体的实例我们不想管,我们只给你一个代表该实例的代号,你就应该生成该实例。这就是工厂模式。
要注意的是,简单工厂模式只是简单的将客户端对类的实例的依赖转移到了工厂类里。这只是实现了部分的开闭原则,对客户端调用来说,满足了开闭原则,而对工厂类来说,没有满足。简单工厂模式这样的弱点,使得我们对该模式进行进一步的优化,才有了抽象工厂和工厂方法等模式。我们也可以结合Java反射对工厂模式进一步优化,使得该模式在更大程度上满足开闭原则。
命令模式、策略模式和状态模式的原理都一样,都是分离关注点:命令模式是对行为的分离,策略模式是对算法的分离,状态模式是对状态的分离。这些关注点的分离,首先是满足了单一职责原则。但是仅仅把这些关注点,如行为、算法和状态等等分离开来就可以了吗?我们以命令模式为例来看看这方面的问题。
我们最初的客户端代码为:
if(condition1)
{

        //do action 1

        ……
}
else if(condition2)
{

        //do action 2

        ……
}
……
然后我们将各个行为分离出来:
public class Action1
{

        public void doAction1()

        {
……
}
}
public class Action2
{

        public void doAction2()

        {
……
}
}
则客户端变为:
if(condition1)
{
       Action1 action = new Action1();
       Action.doAction1();
}
else if(condition2)
{
       Action2 action = new Action2();
       Action.doAction2();
}
这显然没有使客户端代码的扩展性好多少,如果我们增加一种新的行为的话,首先要增加这个行为的类,然后对客户端依然要做相应的修改,不满足我们的开闭原则。要满足开闭原则,我们要做的第一步是对行为类进行进一步的抽象,给这些行为类一个共同的接口:
public interface Action
{

        public void doAction();

}
然后我们的各个行为类都实现Action接口。这样,客户端的代码修改为:
Action ation;

if(condition1) action = new Action1();

else if(condition2) action = new Action2();

……
action.doAction();
这就是命令模式的解决思路,有了接口,就为系统的扩展打下了基础。比如我们上面的客户端代码如果将具体类的实例化转移到一个工厂里,客户端代码就会象下面这样:

Action action = Factory.getInstance(condition);

Action.doAction();
这样,客户端的扩展性就比较好的满足了开闭原则。
组合模式为了系统的扩展性,将整体和部分都实现同一个接口,这样使得不管是整体还是部分都可以扩展,很好的满足了开闭原则。如光盘盒和光盘是“整体和部分”的关系,光盘盒可以作为一个整体,而光盘是一个部分,两者都实现了同一个接口。这样,我们可以对“整体”进行扩展,比如光盘盒可以放在工具箱里面,这里工具箱是一个更大的“整体”,我们可以让它实现上面的接口,不会对系统产生太大的影响。我们也可以扩展“部分”,如工具箱里可以放一些软盘盒,软盘盒里可以放软盘等等。
装配工模式将被装配者和装配者都实现相同的接口,代理模式将代理类和实际的工作类都实现相同的接口,它们的目的都首先是为了满足开闭原则。因为是实现了统一的接口,对于装配者和实际工作类来说,它们都可以扩展更多的类来实现这个接口,从而达到满足系统对扩展性的要求。关于这些模式在细节上是如何实现开闭原则的,在这里也就不多说了,大家可以结合相关的模式来加以理解。

抱歉!评论已关闭.