设计模式-重要原则
- 单一职责原则 (SRP).
- 开放-封闭原则(OCP).
- 依赖倒转原则(DIP).
- 里氏代换原则(LSP).
- 迪米特法则(LOD).
- 接口隔离原则(ISP).
单一职责原则
定义:就一个类而言,应该仅有一个引起它变化的原因。
一个类/接口/方法只负责一个职责。
思想:如果一个类承担的职责太多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。这种耦合会造成脆弱的设计,当变化发生时,设计会受到意想不到的破坏。
优点
- 降低类的复杂度。
- 提高类的可读性,因为类的职能单一,目的性强,代码风格简单。
- 提高系统的可维护性,易复用,易扩展。
缺点
类不可一味追求单一职责原则,如果这样做,当一个项目过大时,会使得类的数量BOOM。而且我们的接口和方法一定要追求这个原则。
开放-封闭原则
定义:对于软件实体(类,模块,函数等)可扩展,但不可修改。即对于扩展功能是开放的,对于修改是封闭的。
思路:无论我们的模块多么的“封闭”,都会存在一些无法对于封闭的变化。既不可能完全封闭,设计人员必须对于其设计的模块封闭要做出选择。他必须猜测出最有可能发生变化的种类,然后构造抽象来隔离这些变化。面对需求时,对程序的改动只通过增加代码实现,而不是更改原来的代码。例子:之前的算法类(简单工厂模式实现)
uml图如下:
后期我们想加入除法类只需要让它继承运算类即可,不需要更改之前的代码。
优势
开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象设计的巨大好处(可维护,可扩展,可复用,灵活性好。)
注意
开发人员应该对程序中呈现频繁变化的那部分进行抽象,然而,对于应用程序中的每一部分都进行抽象也不是一个很好的主意。拒绝不成熟的抽象和抽象同样重要。
依赖倒转原则
定义:抽象不应该依赖细节,细节应该依赖抽象,要针对接口编程,不应该对实现编程。高层模块不应该依赖低层模块,两者都应该抽象。如图:
看个例子:
#include<cstdio>
#include<iostream>
using namespace std;
class CaiName{
public:
void caicontent(){
cout<<"青菜豆腐"<<endl;
}
};
class Mine{
public:
void zuofan(CaiName* name){
printf("我会做");
name->caicontent();
}
};
int main(){
Mine* temp = new Mine;
CaiName* name = new CaiName;
temp->zuofan(name); //输出 “我会做青菜豆腐”
}
我们发现这个程序运行效果非常好,但是随着时间推移,我不可能光会做“青菜豆腐”吧,我肯定还会做别的菜品,比如说“醋溜土豆丝”啊,“手撕包菜”呀等等。这个时候我们就会加入新代码:
class CaiName1{
public:
void caicontent(){
cout<<"醋溜土豆丝"<<endl;
}
};
可是这个时候我们并不会做,因为Mine类和CaiName类的耦合性太强,如果我们需要会做“醋溜土豆丝”,就需要改变Mine类 ,这恰恰是我们不想看到的,破坏了开放-封闭原则。而此时的Mine类就是我们所谓的高层模块。
这个时候怎么办呢?
抽象,抽象,抽象化,破坏它们之间的耦合度!!!!!
class CaiName{
public:
virtual void caicontent()=0;
};
class CaiName1:public CaiName{
public:
void caicontent(){
cout<<"青菜豆腐"<<endl;
}
};
class CaiName2:public CaiName{
public:
void caicontent(){
cout<<"醋溜土豆丝"<<endl;
}
};
class Mine{
public:
void zuofan(CaiName* name){
printf("我会做");
name->caicontent();
}
};
int main(){
Mine* temp = new Mine;
CaiName* name = new CaiName1;
CaiName* name1 = new CaiName2;
temp->zuofan(name);//输出“我会做青菜豆腐”
temp->zuofan(name1);//输出“我会做醋溜土豆丝”
}
这里我们把CaiName类写成纯虚类,抽象化也就成了接口,CaiName1和CaiName2分别继承它。这样做Mine类就不需要更改。后期如果要加入新学会的菜品,只需要新加CaiName系统的类就好,不会动Mine类。
里氏代换原则
定义:一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类和子类的区别。也就是说,在软件里面,把父类替换成子类,程序行为不会发生任何改变。即:子类必须能够替换父类型。
我们以上面例子为例:
先做出uml图:
CaiName* name = new CaiName1;
CaiName* name1 = new CaiName2;
temp->zuofan(name);//输出“我会做青菜豆腐”
temp->zuofan(name1);//输出“我会做醋溜土豆丝”
我们用CaiName1,和CaiName2替换掉了CaiName类。(只有当子类可以替换掉父类,软件单位的功能没有受到影响,父类才能真正的被复用,而子类也可以在父类的基础上,增加新的行为。)
换句话说:正是因为子类型的可替换性才使得父类的模块在无需修改的情况下可扩展。
迪米特法则
定义:如果两个类不必彼此直接通信,那么这两个类就不应该发生直接的相互作用,如果其中一个类需要调用一个类的某一个方法,可以通过第三者转发这个调用。
总结
依赖倒转其实可以说是面向对象设计的标志,用那种编程语言不重要,如果编写时都是考虑如何针对抽象编程而不是细节编程,也就是程序中的所有依赖关系终止于抽象类和接口,就可以说是面向对象的设计,反之就是面向过程。