六大设计原则(SOLID)
- Single Responsibility Principle:单一职责原则,实现类要指责单一。
- Open Closed Principle:开闭原则,对扩展开发,对修改关闭。
- Liskov Substitution Principle:里氏替换原则,不要破坏继承关系。
- Law of Demeter:迪米特法则,要降低耦合度。
- Interface Segregation Principle:接口隔离原则,在设计接口的时候要精简单一。
- Dependence Inversion Principle:依赖倒置原则,面向接口编程。
把这六个原则的首字母联合起来(两个 L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。
一、单一职责原则
一个类应该只有一个发生变化的原因
There should never be more than one reason for a class to change.
一个类只负责一个职责,每个类只需要负责自己的那部分,类的复杂度就会降低。如果职责互粉得很清楚,那么代码维护起来也更加容易。试想如果所有的功能都放在了一个类中,那么这个类就会变得非常臃肿,而且一旦发现bug,要在所有代码中去寻找;更改某个地方,可能要改变整个代码的结构,想想都非常可怕。当然一般时候,没有人会去这么写。
这个原则不仅仅适用于类,对于接口和方法也适用,即一个接口/方法,只负责一件事,这样的话,接口就会变得简单,方法中的代码也会更少,易读,便于维护。
事实上,由于一些其他的因素影响,类的单一职责在项目中是很难保证的。通常,接口和方法的单一职责更容易实现。
单一职责原则的好处:
- 代码的粒度降低了,类的复杂度降低了
- 可读性提高了,每个类的职责都很明确,可读性自然更好
- 可维护性提高了,可读性提高了,一旦出现bug,自然更容易找到他问题所在
- 改动代码所消耗的资源降低了,更改的风险也降低了
二、开闭原则
一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。
Software entities like classes, modules and functions should be open for extension but closed for modification
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改,可能会给旧代码引入错误,也有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。这是引入开闭原则的原因,用抽象构建构架,用实现扩展细节,因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保证架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了,当然前提是抽象要合理,要对需求的变更有前瞻性和预见性。
三、里氏替换原则定义
所有引用基类的地方必须能透明地使用其子类的对象
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
里氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。
继承的优点:
- 子类拥有父类的所有方法和属性从而可以减少创建类的工作量。
- 提高了代码的重用性。
- 提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。
继承的缺点:
- 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
- 降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
- 增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。
里氏替换原则对继承进行了规则上的约束
里氏替换原则对继承进行了规则上的约束,这种约束主要体现在四个方面:
- 子类必须实现父类的抽象方法,但不得覆盖父类的非抽象方法。
- 子类中可以增加自己持有的方法。
- 当子类覆盖或实现父类的方法时,方法的前置条件要比 - 父类方法的输入参数更宽松
- 当子类的方法实现父类的抽象方法时,方法的后置条件要比父类更严格。
四、迪米特法则
只与你的直接朋友交谈,不跟“陌生人”说话
Talk only to your immediate friends and not to strangers
如果两个软件实体无须直接通信,那么就不应当发生直接的互相调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
注意事项:过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在采用迪米特法则时需要反复权衡,确保高内聚和低耦合,保证系统的结构清晰。
迪米特法则的实现方法:
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
例子:一个中介,客户只要找中介要满足的楼盘,而不必跟每个楼盘发生联系。
五、接口隔离原则
1、客户端不应该依赖它不需要的接口
2、类间的依赖关系应该建立在最小的接口上
Clients should not be forced to depend upon interfaces that they don`t use.
The dependency of one class to another one should depend on the smallest possible.
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责的区别
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者不同的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 单一个则原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
接口隔离原则的优点
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有一下5个优点:
- 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合度。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整个项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
接口隔离原则的实现方法
- 根据接口隔离原则拆分接口时,首先必须满足单一职责原则
- 接口尽量小,但是要有限度,一个接口只服务于一个子模块或业务逻辑
- 为依赖接口的类定制服务,只提供调用者需要的方法,屏蔽不需要的方法
- 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情
六、依赖倒置原则
1、上层模块不应该依赖底层模块,它们都应该依赖于抽象
2、抽象不应该依赖于细节,细节应该依赖于抽象
High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.
举个例子,现在你需要实现一个比萨店,你第一件想到的事情是什么?我想到的是一个比萨店,里面有很多具体的比萨,如:芝士比萨、素食比萨、海鲜比萨……
比萨店是上层模块,比萨是下层模块,如果把比萨店和它依赖的对象画成一张图,看起来是这样:
没错!先从顶端开始,然后往下到具体类,但是,正如你看到的你不想让比萨店理会这些具体类,要不然比萨店将全都依赖这些具体类。现在“倒置”你的想法……别从上层模块比萨店开始思考,而是从下层模块比萨开始,然后想想看能抽象化些什么。你可能会想到,芝士比萨、素食比萨、海鲜比萨都是比萨,所以它们应该共享一个Pizza接口。对了,你想要抽象化一个Pizza。好,现在回头重新思考如何设计比萨店。
此例子也很好的解释了“上层模块不应该依赖底层模块,它们都应该依赖于抽象。”,在最开始的设计中,高层模块PizzaStroe直接依赖低层模块(各种具体的Pizaa),调整设计后,高层模块和低层模块都依赖于抽象(Pizza)
参考:
https://www.cnblogs.com/LangZXG/p/6242927.html
//www.greatytc.com/p/3268264ae581