软件实体应当对扩展开放,对修改关闭
含义:
对于扩展是开放的(Open for extension)。也就是模块的行为是可以扩展的,当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。
对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
当项目需求变动时,在不修改源代码前提下,通过增加新类/新方法/新模块等方式满足新的需求,而不是修改。
最基础的设计原则,前面讲的6个设计原则,跟后面讲设计模式目的都是为让程序(架构)能遵循开闭原则,或者说其他的原则都是开闭原则的具体形态。
开闭原则是面向对象程序设计的终极目标,要求程序(架构)既要拥有一定的适应性和灵活性,又要具备稳定性和延续性
方便测试,遵循开闭原则的程序模块只需要独立测试拓展模块即可
提高复用性,符合开闭原则的组件设计,一般都是高内聚低耦合的设计
开闭原则的实现基本围绕着抽象约束,封装变化这个思想展开的。即通过接口/抽象类来定义一个统一的规范,对功能做约束,而需要变动的因素或个体差异通过具体实现类/子类体现。
一个饲养员喂羊动物的小案例
#include
class Dog{
public:
void eat() {
std::cout << "狗吃骨头" << std::endl;
}
};
class Cat{
public:
void eat() {
std::cout << "猫吃鱼" << std::endl;
}
};
class Keeper{
public:
void feedDog(Dog* dog) {
dog->eat();
}
void feedCat(Cat* cat) {
cat->eat();
}
};
int main(){
Cat* cat = new Cat;
Dog* dog = new Dog;
Keeper* keeper = new Keeper;
keeper->feedDog(dog);
keeper->feedCat(cat);
return 1;
}
分析:这是一个很简答的小例子,一个猫类一个狗类,一个饲养员类,猫和狗具有吃的方法,饲养员具有喂猫和喂狗两种方法,main中调用也符合预期。这是现有的功能(旧功能),想象一下,当动物园新进一种动物,比如小鱼,代码应当如何变化呢
新增小鱼类,也具有吃的方法;新增类,属于模块级变化,设计模式中允许对拓展开放;
修改keeper类,增加喂小鱼的方法;keeper类属于旧的功能类,但是随着新需求的增加要被修改,违背了原则中的对修改关闭;
main中使用。
同时,该例子也违背了
高层次的模块不要依赖低层次的模块,二者都应该依赖于其抽象(案例中 keeper和dog都是具体类,keeper依赖dog类,因为dog类变化后,keeper可能会出问题)
抽象不应该依赖于具体,而是具体应该依赖于抽象(具体类依赖了具体类,而不是依赖抽象类)
#include
class Animal{
public:
virtual void eat() = 0; //纯虚函数
};
class Dog : public Animal{
public:
virtual void eat() override {
std::cout << "狗吃骨头" << std::endl;
}
};
class Cat : public Animal{
public:
virtual void eat() override {
std::cout << "猫吃鱼" << std::endl;
}
};
class Keeper{
public:
void feedAnimal(Animal* a) {
a->eat();
}
};
int main(){
Animal* cat = new Cat;
Animal* dog = new Dog;
Keeper* keeper = new Keeper;
keeper->feedAnimal(dog);
keeper->feedAnimal(cat);
return 1;
}
分析:从狗类和猫类中抽象出来动物类