设计原则名称 | 作用 |
---|---|
单一职责原则 | 类的职责要单一,不能将太多的职责放在一个类中 |
开闭原则 | 软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能 |
里氏代换原则 | 在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象 |
依赖倒转原则 | 要针对抽象层编程,而不要针对具体类编程(抽象不应该依赖细节,细节应该依赖抽象) |
接口隔离原则 | 使用多个专门的接口来取代一个统一的接口 |
合成复用原则 | 在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系 |
迪米特法则 | 一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互 |
作为一个Android工程师,如果不仅让我开发Android,还要从需求调研、到原型设计、再到测试、发布都一个人干。就好像一个“牛”类一般,身兼多职,可是工资不变又不给多发福利。而且“牛”类还有一个弊端就是越“牛”就越“牛”,领导有事第一个想到让你解决,你承担的工作也越来越多。当然相对的,做得越多错得越多,被领导批的也越多。多多少少这个“牛”类都会有所怨言,然后工作积极性下降,导致项目越做越糙。最后不是自己受不了离职滚蛋了,就是公司忍不了开了这个“牛”类。或者双方不离不弃,最后一起堕入地狱。
把整个项目想想成为一个公司,然后我们每一个职员在工作中则负责各司其职。回到前面所说的工作量:
单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责的优点:
类的复杂性降低,实现什么职责都有清晰明确的定义。
可读性提高,复杂性降低。可维护性提高
变更引起的风险降低,变更是必不可少的,如果接口的单一职责做的好,一个接口修改只对相应的实现类有影响,
以软件开发流程简单说明:
1、需求分析:需求调研
2、开发人员:程序开发
3、测试人员:测试
#include
using namespace std;
class softwareDevelop
{
public:
void DemandResearch() {
cout << "DemandResearch complete......" << endl;
}
void Programing() {
cout << "Programing complete......." << endl;
}
void Testing() {
cout << "Testing complete........" << endl;
}
};
int main(void)
{
softwareDevelop pro;
pro.DemandResearch();
pro.Programing();
pro.Testing();
return 0;
}
#include
using namespace std;
class Demand
{
public:
void DemandResearch() {
cout << "DemandResearch complete......" << endl;
}
};
class Program
{
public:
void Programing() {
cout << "Programing complete......." << endl;
}
};
class Test
{
public:
void Testing() {
cout << "Testing complete........" << endl;
}
};
int main(void)
{
Demand d;
d.DemandResearch();
Program p;
p.Programing();
Test t;
t.Testing();
return 0;
}
在现实生活中,什么是父子?就是生你的那个男人和你的关系就是父子(父女)。而这里定义的就是假如 A 能胜任 B 干的所有事情,那 B 就是 A 的父亲,也就是儿子要会父亲的所有能活,儿子活得再烂也要有父亲的水平。
价值观 :很显然,比较传统,严父出孝子。儿子必须要有父亲的能耐,最好青出于蓝胜于蓝。里氏替换原则定义了什么是父子,还有一点要注意的,就是儿子不能在父亲会的技能上搞“创新”。
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。
由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
尽量把父类设计成抽象类或接口,让子类继承父类或实现父接口。增加一个新功能时,通过增加一个新的子类来实现。
子类中可以增加自己特有的方法。
子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
优点
缺点
参考
(“抽象约束、封装变化”)
为什么要“开”和“闭”
一般情况,我们接到需求变更的通知,通常方式可能就是修改模块的源代码,然而修改已经存在的源代码是存在很大风险的,尤其是项目上线运行一段时间后,开发人员发生变化,这种风险可能就更大。所以,为了避免这种风险,在面对需求变更时,我们一般不修改源代码,即所谓的对修改关闭。不允许修改源代码,我们如何应对需求变更呢?答案就是我们下面要说的对扩展开放。
通过扩展去应对需求变化,就要求我们必须要面向接口编程,或者说面向抽象编程。所有参数类型、引用传递的对象必须使用抽象(接口或者抽象类)的方式定义,不能使用实现类的方式定义;通过抽象去界定扩展,比如我们定义了一个接口A的参数,那么我们的扩展只能是接口A的实现类。总的来说,开闭原则提高系统的可维护性和代码的重用性。
一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭.即一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化.
#include
using namespace std;
/*
如果需要增加新的功能,需要再次添加新的成员函数,会导致类越来越复杂
*/
class BankWorker {
public:
void save() {
cout << "save money." << endl;
}
void transter() {
cout << "transfer money." << endl;
}
void pay() {
cout << "pay money." << endl;
}
/*
如果在后期需要增加网银开通、贷款等业务,则需要在此处继续添加函数。
*/
};
int main()
{
BankWorker *bw = new BankWorker;
bw->pay();
bw->transter();
bw->save();
delete bw;
bw = NULL;
system("pause");
return 0;
}
问题来了:对于银行业务员类(BankWorker)的设计就违背了开闭原则。因为如果后期需要添加新的功能,就不得不修改类的源代码。
符合开闭原则的思路设计代码
#include
using namespace std;
class BankWorker {
public:/*纯虚函数的设计用来抽象银行业务员的业务*/
virtual void doBusiness() = 0;
};
/*创建存钱的银行员*/
class saveBankWorker : public BankWorker {
public:
virtual void doBusiness() {
cout << "save money." << endl;
}
};
/*创建转账的银行员*/
class transferBankWorker : public BankWorker {
public:
virtual void doBusiness() {
cout << "transfer money." << endl;
}
};
/*创建取钱的银行员*/
class payBankWorker :public BankWorker {
public:
virtual void doBusiness() {
cout << "pay money." << endl;
}
};
/*后期如果需要增加新的功能,只需要再次集成一个新类实现业务函数即可*/
/*新增办理基金的银行员*/
class fundationBankWorker :public BankWorker {
virtual void doBusiness() {
cout << "fundation money." << endl;
}
};
int main()
{
/*
C++产生多态的3个必要条件
1、有继承,如saveBankWorker继承了BankWorker
2、要有重写,这里的BankWorker类的doBusiness()函数是纯虚函数,
就会被重写,这个函数也称之为接口函数
3、父类指针指向子类对象
*/
BankWorker *bw = NULL; //实例化一个父类指针
bw = new saveBankWorker; //将父类指针指向子类对象
bw->doBusiness(); //调用业务函数
delete bw; //释放空间
bw = NULL; //将指针指向空,更加安全
bw = new transferBankWorker;
bw->doBusiness();
delete bw;
bw = NULL;
bw = new payBankWorker;
bw->doBusiness();
delete bw;
bw = NULL;
system("pause");
return 0;
}
举个例子,现在你需要实现一个比萨店,你第一件想到的事情是什么?我想到的是一个比萨店,里面有很多具体的比萨,如:芝士比萨、素食比萨、海鲜比萨……
比萨店是上层模块,比萨是下层模块,如果把比萨店和它依赖的对象画成一张图,看起来是这样:
没错!先从顶端开始,然后往下到具体类,但是,正如你看到的你不想让比萨店理会这些具体类,要不然比萨店将全都依赖这些具体类。现在“倒置”你的想法……别从上层模块比萨店开始思考,而是从下层模块比萨开始,然后想想看能抽象化些什么。你可能会想到,芝士比萨、素食比萨、海鲜比萨都是比萨,所以它们应该共享一个Pizza接口。对了,你想要抽象化一个Pizza。好,现在回头重新思考如何设计比萨店。
图一的依赖箭头都是从上往下的,图二的箭头出现了从下往上,依赖关系确实“倒置”
了
另外,此例子也很好的解释了“上层模块不应该依赖底层模块,它们都应该依赖于抽象。”,在最开始的设计中,高层模块PizzaStroe直接依赖低层模块(各种具体的Pizaa),调整设计后,高层模块和低层模块都依赖于抽象(Pizza)
上层模块不应该依赖底层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
假设现在组装一台电脑需要cpu,硬盘,内存,这三种器件可以相互对接(我的意思是硬件设备可以直接连接使用),然后电脑厂商可以根据不同的cpu,硬盘,内存进行搭配不同样式的电脑,在后期的升级和维护中,可能会有新的cpu、硬盘、内存品牌增加进行相互组合。
#include "iostream"
using namespace std;
/*
抽象层(中间层)
*/
class HardDisk{
public:
virtual void work()=0;
};
class Memory{
public:
virtual void work()=0;
};
class Cpu{
public:
virtual void work()=0;
};
/*
让Computer 框架和具体的电脑产商 解耦合
*/
/*
高层架构层,依赖于抽象层(中间层)
*/
class Computer{
public:
Computer(Cpu *mycpu, Memory *mem, HardDisk *hard){
m_cpu = mycpu;
m_mem = mem;
m_hard = hard;
}
//高层业务函数,只关心每个硬件的业务(是否工作等),并不关心硬件是那些个产商生产的
void work(){
m_cpu->work();
m_mem->work();
m_hard->work();
}
private:
Cpu *m_cpu=NULL;
Memory *m_mem=NULL;
HardDisk *m_hard=NULL;
};
/*
实现层(底层),只需要依赖于中间抽象层,实现抽象层的方法
*/
class XiJieHardDisk :public HardDisk{
public:
virtual void work(){
cout << "XiJie HardDisk working..." << endl;
}
};
class InterCpu :public Cpu{
public:
virtual void work()
{
cout << "Inter Cpu working..." << endl;
}
};
class JSDMemory :public Memory{
public:
virtual void work()
{
cout << "JSD Memory working" << endl;
}
};
int main()
{
XiJieHardDisk *xjdisk = new XiJieHardDisk;
InterCpu *intercpu = new InterCpu;
JSDMemory *jsdmemory = new JSDMemory;
Computer *myComputer = new Computer(intercpu, jsdmemory, xjdisk);
myComputer->work();
delete xjdisk;
delete intercpu;
delete jsdmemory;
delete myComputer;
system("pause");
return 0;
}
利用此种方法可以完美的解决后期新的品牌加入问题,同时将底层和高层进行分离,可以更好的管理自己的代码。
1、https://www.cnblogs.com/WindSun/p/10223080.html
2、https://www.cnblogs.com/dolphin0520/p/3919839.html
3、https://www.jianshu.com/p/02926f3a5c1d
4、https://www.cnblogs.com/liebrother/p/10193334.html
5、https://zhuanlan.zhihu.com/p/24269134
6、https://blog.csdn.net/qq_21078557/article/details/78257558
7、https://www.cnblogs.com/lsgxeva/p/7773009.html
8、https://www.jianshu.com/p/c3ce6762257c
9、《大话设计模式》