设计原则主要从思想层面指导面向对象分析设计,设计模式则是针对某个场景下的某些问题的某个解决方案,是基于设计原则的具体实现。
常见的六大面向对象设计原则
单一职责原则 SRP(Single Responsibility Principle)
一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。
通俗地说,即一个类只负责一项职责。比如下单是一个职责,由专门的下单类负责,付款是另一个职责,由专门的付款类负责,而不是下单逻辑与付款逻辑混杂在同一个类中。
解决的问题
如果一个类负责多个职责,当因为职责A的需求发生改变而需要修改代码的时候,有可能会导致原本运行正常的职责B功能发生故障。
实现方法
职责单一看上去非常简单,但也是因为太简单,反而很难完全做到,难度在于如何区分职责,职责的粒度如何量化,这是个没有标准化的东西。下单原本是一个职责,但下单方式会有很多种,不同的下单方式是否需要再细分?如果需要细分,那么下单类需要拆分成多个实现类,如果是在原有代码的基础上做拆分,就不仅需要修改下单类代码,还需要修改所有调用方代码,十分消耗时间与精力。该如何遵守该设计原则,还得与实际情况结合。
单一职责原则不仅适用的类,也可以小到适用于类中的一个方法,大到适用于一个微服务,一个系统,设计原则重要还是指导意义。
优点
- 降低复杂度,职责单一,可以更好地理解业务,也可以更简单地实现业务逻辑,与上下游的联系通过接口定义,开发人员只需要关注内部业务逻辑实现即可,在业务理解及实现上都可以更加精确。
- 提高类的可读性,复杂度降低,可阅读性相应就会增强
- 提高系统的可维护性,复杂度降低,可维护性相应增加
- 降低变更可能引起的风险
开放关闭原则 OCP(Open-Closed Principle)
软件实体应当对扩展开放,对修改关闭
开放关闭原则又称开闭原则,这是设计中非常核心的一个原则。
解决的问题
如果需求变化的时候,在不修改已有代码的情况下进行扩展。
实现方法
实现开闭原则的关键在于合理地抽象,分离出变化与不变的部分,为变化的部分预留下可扩展的方式,比如钩子方法或者动态组合对象等。
适度的抽象可以提高系统的灵活性,但是过度抽象会大大增加系统的复杂程度,在设计时需要避免陷入过度设计。
优点
- 提高代码的可复用性
- 提高系统的可维护性
里氏替换原则 LSP(Liskov Substitution Principle)
继承必须确保超类所拥有的性质在子类中仍然成立
通俗地说,子类必须能够替换掉其父类,当一个类继承了另一个类,那么子类就应该拥有父类中可以继承的属性的操作,理论上来说,此时使用子类型去替代掉父类型,应该不会引起原来使用父类型的程序出现错误。这是一个针对如何正确使用继承的设计原则。
解决的问题
父类新增了一个子类,可能导致原来使用父类的地方产生错误,原因是该子类重写了父类中的某些方法导致。
比如有一个电子表类,有一个方法为调整时间,本来运行正常,此时新增一个子类塑料表,虽然挂名电子表,但其实并不能对其调整时间,此时需要考虑这个继承关系到底有没有问题。
实现方法
子类可以扩展父类的功能,但不能改变父类原有的功能,如果通过重写父类的方法来实现新的功能,会导致整个继承体系的可复用性变差,此时应该考虑是否使用组合替代继承。
优点
从另外一个角度来说,里氏替换原则是实现开闭原则的重要方式之一。开闭原则要求对扩展开放,扩展的一个实现手段就是继承,而里氏替换原则保证子类型能够正确替换父类型,只有正确替换,才能实现扩展,否则扩展了也会出现错误。
依赖倒置原则 DIP(Dependence Inversion Principle)
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
通俗地说,要依赖抽象,不要依赖具体实现
解决的问题
由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指的是接口或抽象类,而细节则是指具体的实现类。
使用接口或抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
实现方法
- 高层模块不应该依赖于底层模块,二者都应该依赖于抽象
- 抽象不应该依赖于具体实现,具体实现应该依赖于抽象
底层接口应该是由高层提出的,然后由底层实现的,也就是说底层的接口的所有权在高层模块,因此是一种所有权的倒置。
优点
- 降低类间的耦合性
- 提高系统的稳定性
- 减少并行开发引起的风险
- 提高代码的可读性和可维护性
接口隔离原则 ISP(Interface Segregation Principle)
客户端不应该被迫依赖于它不使用的方法
解决的问题
这个原则用来处理那些比较 庞大 的接口,这种接口通常会有较多的操作声明,涉及到很多的职责。客户在使用这样的接口的时候,通常会有很多他不需要的方法,这些方法对于客户来讲,就是一种接口污染,相当于强迫用户在一大堆 垃圾方法 中寻找需要的方法。
实现方法
尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法
优点
接口隔离原则和单一职责原则都是为了提高类的内聚性,降低它们之间的耦合性,体现了封装的思想,但两者是不同的,不同在于:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要是约束接口,针对抽象和程序整体框架的构建
最少知识原则 LKP(Least Knowledge Principle)
只与你的直接朋友交谈,不跟“陌生人”说话
如果两个类无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
这个原则
解决的问题
最少知识原则又称迪米特法则,减少对象之间的交互,只跟自己的朋友交互,降低类之间的耦合度,这样修改系统某一个部分的时候,就不会影响其它的部分,从而使得系统具胡更好的可维护性。
实现方法
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
优点
- 降低类之间的耦合度,提高类的可复用率和系统的扩展性
- 提高模块的相对独立性
但是,如果过度使用迪米特原则,会使系统产生大量的中介类,导致系统的复杂度增加和模块之间的通信效率降低。
其它原则
- 面向接口编程
- 优先使用组合,而非继承
结束语
设计原则只是一个建议指导。事实上,在实际开发中,很少做到完全遵守,还要综合考虑业务功能、实现难度、系统性能、时间与空间等,所以总会在有意无意间违反部分设计原则。
设计工作本来就是一个不断权衡的工作。