什么是开放-封闭原则(ocp)

定义:软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
思想:当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许
改动程序的源代码。

用对象的多态性消除条件分支

过多的条件分支语句是造成程序违反开放-封闭原则的一个常见原因。每当需要增加一个新的if语句时,都要被迫改动原函数。把if换
成switch-case是没有用的,这是一种换汤不换药的做法。实际上,每当我们看到一大片的if或者swtich-case语句时,第一时间就应该考虑,能否利用对象的多态性来重构它们。

利用对象的多态性来让程序遵守开放-封闭原则,是一个常用的技巧。我们依然选用让动物发出叫声的例子。下面先提供一段不符合开放-封闭原则的代码。每当我们增加一种新的动物时,都需要改动makeSound函数的内部实现:

var makeSound = function( animal ){
    if ( animal instanceof Duck ){
        console.log( '嘎嘎嘎' );
    }else if ( animal instanceof Chicken ){
        console.log( '咯咯咯' );
    }
};
var Duck = function(){};
var Chicken = function(){};
makeSound( new Duck() ); // 输出:嘎嘎嘎
makeSound( new Chicken() ); // 输出:咯咯咯

动物世界里增加一只狗之后,makeSound函数必须改成:

var makeSound = function( animal ){
    if ( animal instanceof Duck ){
        console.log( '嘎嘎嘎' );
    }else if ( animal instanceof Chicken ){
        console.log( '咯咯咯' );
    }else if ( animal instanceof Dog ){ // 增加跟狗叫声相关的代码
        console.log('汪汪汪' );
    }
};
var Dog = function(){};
makeSound( new Dog() ); // 增加一只狗

利用多态的思想,我们把程序中不变的部分隔离出来(动物都会叫),然后把可变的部分封装起来(不同类型的动物发出不同的叫声),这样一来程序就具有了可扩展性。当我们想让一只狗发出叫声时,只需增加一段代码即可,而不用去改动原有的makeSound函数:

var makeSound = function( animal ){
    animal.sound();
};
var Duck = function(){};
Duck.prototype.sound = function(){
    console.log( '嘎嘎嘎' );
};
var Chicken = function(){};
Chicken.prototype.sound = function(){
    console.log( '咯咯咯' );
};
makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯
/********* 增加动物狗,不用改动原有的makeSound函数 ****************/
var Dog = function(){};
Dog.prototype.sound = function(){
    console.log( '汪汪汪' );
};
makeSound( new Dog() ); // 汪汪汪

找出变化的地方

开放-封闭原则是一个看起来比较虚幻的原则,并没有实际的模板教导我们怎样亦步亦趋地实现它。但我们还是能找到一些让程序尽量遵守开放-封闭原则的规律,最明显的就是找出程序中将要发生变化的地方,然后把变化封装起来。

通过封装变化的方式,可以把系统中稳定不变的部分和容易变化的部分隔离开来。在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经被封装好的,那么替换起来也相对容易。而变化部分之外的就是稳定的部分。在系统的演变过程中,稳定的部分是不需要改变的。

除了利用对象的多态性之外,还有其他方式可以帮助我们编写遵守开放-封闭原则的代码:

  1. 放置挂钩
  2. 使用回调函数

设计模式中的开放-封闭原则

有一种说法是,设计模式就是给做的好的设计取个名字。几乎所有的设计模式都是遵守开放-封闭原则的,我们见到的好设计,通常都经得起开放-封闭原则的考验。不管是具体的各种设计模式,还是更抽象的面向对象设计原则,比如单一职责原则、最少知识原则、依赖倒置原则等,都是为了让程序遵守开放-封闭原则而出现的。可以这样说,开放封闭原则是编写一个好程序的目标,其他设计原则都是达到这个目标的过程

  1. 发布-订阅模式

发布-订阅模式用来降低多个对象之间的依赖关系,它可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。当有新的订阅者出现时,发布者的代码不需要进行任何修改;同样当发布者需要改变时,也不会影响到之前的订阅者。

  1. 模板方法模式

模板方法模式是一种典型的通过封装变化来提高系统扩展性的设计模式。在一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽出来放到父类的模板方法里面;而子类的方法具体怎么实现则是可变的,于是把这部分变化的逻辑封装到子类中。通过增加新的子类,便能给系统增加新的功能,并不需要改动抽象父类以及其他的子类,这也是符合开放-封闭原则的。

  1. 策略模式

策略模式和模板方法模式是一对竞争者。在大多数情况下,它们可以相互替换使用。模板方法模式基于继承的思想,而策略模式则偏重于组合和委托。策略模式将各种算法都封装成单独的策略类,这些策略类可以被交换使用。策略和使用策略的客户代码可以分别独立进行修改而互不影响。我
们增加一个新的策略类也非常方便,完全不用修改之前的代码。

  1. 代理模式
  2. 职责链模式

开放-封闭原则是相对的,没有完全完美的开放-封闭原则,越完美程序复杂度越高