定义
开闭原则(OCP)是面向对象编程中最基础和最重要的设计原则之一。我们在设计程序中使用一些设计模式和遵守相关的设计原则,都是为了可以使我们的程序可以实现“开闭”!说了这么多,那到底什么是开闭原则那?定义如下:
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改
初看这个定义,可能会很迷惑,其实这句话的开放和关闭是可以拆开来讲的。
对扩展开放:指的是我们系统中的模块、类、方法对它们的提供者(开发者)应该是开放的,提供者可以对系统进行扩展(新增)新的功能。
对修改关闭:指的是系统中的模块、类、方法对它们的使用者(调用者)应该是关闭的。使用者使用这些功能时,不会因为提供方新增了功能而导致使用者也进行相应修改。
为什么要使用开闭原则
- 开闭原则非常有名,只要是做面向对象编程的,在开发时都会提及开闭原则。
- 开闭原则是最基础的一个原则,可以说开闭原则是六大设计原则的精神领袖。
- 开闭原则提高测试效率,原有代码不做修改,仅通过扩展实现变化,不需要测试全部回笼测试。
- 开闭原则提高代码的复用性和可维护性。
代码实践
假设现在有水果店,出售苹果和香蕉。代码如下:
class FruitShop {
public sellFruit(fruit) {
if (fruit.type === 1) {
this.sellApple(fruit);
} else if (fruit.type === 2) {
this.sellBanana(fruit)
}
}
public sellApple(fruit) {
console.log('卖出苹果', fruit);
}
public sellBanana(fruit) {
console.log('卖出香蕉', fruit);
}
}
class Fruit{
public type;
}
class Apple extends Fruit {
Apple() {
super.type = 1;
}
}
class Banana extends Fruit {
Banana() {
super.type = 2;
}
}
假如现在水果店需要增加新品种-西瓜,根据上面的示例代码,我们需要做出以下调整:
- 新增西瓜类:
class Watermelon extends Fruit {
Watermelon() {
super.type = 3;
}
}
- 在FruitShop类中添加买西瓜的方法
public sellWatermelon(fruit) {
console.log('卖出西瓜', fruit);
}
- 修改FruitShop类中的sellFruit方法。
public sellFruit(fruit) {
if (fruit.type === 1) {
this.sellApple(fruit);
} else if (fruit.type === 2) {
this.sellBanana(fruit)
} else if (fruit.type === 3) {
this.sellWatermelon(fruit)
}
}
通过以上三步就实现了增加一种水果的需求,但是这种方式虽然容易理解,可是当功能发生变动时,代码的修改量会特别大。并且这种方式也不符合“开闭原则”。
各种各样的水果类就是“提供者”,FruitShop类就是使用这些水果类的“使用者”。开闭原则中,提到对修改关闭,但是现在当提供者新增功能后,使用者(FruitShop类)也需要修改,这就违背了该原则。
我们可以将以上代码进行如下优化:
class FruitShop {
public sellFruit(fruit) {
fruit.sell()
}
public sellApple(fruit) {
console.log('卖出苹果', fruit);
}
public sellBanana(fruit) {
console.log('卖出香蕉', fruit);
}
public sellWatermelon(fruit) {
console.log('卖出西瓜', fruit);
}
}
class Fruit{
public type;
public sell() {
}
}
class Apple extends Fruit {
Apple() {
super.type = 1;
}
public sell() {
console.log('卖出苹果');
}
}
class Banana extends Fruit {
Banana() {
super.type = 2;
}
public sell() {
console.log('卖出香蕉');
}
}
此时当我们新增一种水果(西瓜)时,只需要提供者新增一个继承自Fruit类的子类就可以了。
class Watermelon extends Fruit {
Watermelon() {
super.type = 3;
}
public sell() {
console.log('卖出西瓜');
}
}
现在新增水果的开发完成了,我们并没有修改原来的程序,而是增加了一个继承自Fruit类的子类而已。这样既保护了原代码的结构和完整性,又保证了原代码的可维护性,同时减少了bug产生的机会。
通过上面的描述相信大家能看出,开闭原则给我们传递的思想就是:尽量通过扩展软件的模块、类、方法,来实现功能的变化,而不是通过修改已有的代码来完成。这样做就可以大大降低因为修改代码而给程序带来的出错率。