面向对象设计原则之开闭原则

两截门--一个被水平分割为两部分的门,这样每一部分都可以独立保持开放或封闭

开放-封闭原则(The Open-Closed Principle)

软件实体(类、模块、函数)应该是可以扩展的,但是不可以修改的。
如果程序中的一处改动就会产生连锁反应,导致一系列的相关模块的改动,那么设计就具有僵化的臭味。如果正确的应用OCP,那么以后再进行同样的改动时,就只需要添加新的代码,而不必改动已经正常运行的代码。

描述

主要两个特征:

  1. “对于扩展是开放的”
    模块的行为是可以扩展的,当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。简言之,我们可以改变模块的功能。
  2. “对于更改是封闭的”
    对模块进行扩展时,不必改动模块的源代码。

关键是抽象

下图展示了一个简单的不遵循OCP的设计。Client类和Server类都是具体类,Client类使用Server类。如果我们希望Client对象使用另外一个不同的服务器对象,那么就必须要把Client类中使用Server类的地方更改为新的服务器类

下图是根据OCP设计重构的设计

ClientInterface类是一个拥有抽象成员函数的抽象类,Client类使用这个抽象类。如果我们希望Client对象使用一个不同的服务器类,那么只需要冲ClientInterface类派生一个新的类,无需对Client类做任何改动。

Shape应用程序

应用程序中有Circle、Square列表,需求是:绘制他们
从OCP原则考虑,设计方案如下,

class Shape{
draw();
}

class Circle extends Shape{
    draw(){
    
    }
}

class Square extends Shape{
    draw(){

    }
}

Class DrawAllShape{
    void drawAllShapes(List<Shape> lists){
        for(Shape shape :lists){
            shape.draw();
        }
    }
}

根据此设计,如果新增一个Triangle类,只需新增代码即可,无需改动上述代码,达到了开闭原则。真的吗???

并非100%封闭

如果需求要求所有圆必须在正方形之前绘制,那么DrawAllShape无法对这种变化做到封闭。要实现这个需求,我们需要修改DrawAllShape,使它首先扫描列表中的圆,然后在扫描所有的正方形。

预测变化和贴切的结构

一般来说,无论模块是多么封闭,都会存在一些无法对之封闭的变化,没有对于所有的情况都贴切的模型。
既然不能完全封闭,就要有策略的对待这个问题,设计人员必须对于他设计的模块应该对哪种变化封闭作出选择,他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。这需要设计人员具备一些从经验中获得的预测能力。
遵循OCP的代价是昂贵的,创建正确的抽象是要花费开发时间和精力的,同时,那些抽象也增加了软件设计的复杂性,开发人员有能力处理的抽象的数量也是有限的,显然,我们希望把OCP的应用限定在可能会发生的变化上。
  • 只受一次愚弄
    最初编写代码的时候,假设变化不会发生,当变化发生了,我们就创建抽象来隔离以后发生的同类变化。
  • 刺激变化
    尽早查明可能发生的变化,尽早接受变化带来的改变。

Shape改造

class Shape{
    draw();
    //precedes();
    precedes(OrderRule order){
    }
}
Class DrawAllShape{
    sort(list);
    void drawAllShapes(List<Shape> lists){
        for(Shape shape :lists){
            shape.draw();
        }
    }
}

precedes()考虑到枚举排序的话违背了OCP,可以将规则定义起来,改造成precedes(OrderRule order),排序规则从OrderRule中读取,此时对与Shape绘制顺序的变化不封闭的唯一部分就是OrderRule。

结论

在许多方面,OCP都是面向对象设计的核心所在。遵循这个原则可以的带来面向对象技术所声称的巨大好处(灵活性,可重用性,可维护性)。对于应用程序中的每个部分都肆意的进行抽象同样不是一个好主意。正确的做法是,开发人员应该仅仅对程序中呈现出频繁变化的那些部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。
posted @ 2017-03-17 21:06  vincent_ren  阅读(3452)  评论(0编辑  收藏  举报