前言
本文内会介绍一些在面向对象设计中常用到的设计原则。这些原则能够使对业务逻辑的抽象更加的直观,准确,同时还能够使设计更加符合面向对象设计的理念。笔者在文中也会阐明自己对于这些原则的理解,若有误希望大家随时纠正。
(目录)
1.SRP
单一职责原则:类、方法只干一件事
笔者认为这个原则告诉我们,在封装方法时,尽量不要将大部分或所有的业务逻辑都封装在一个方法中
。应该适当的将逻辑拆分,封装到多个方法中。当然,要注意拆分的力度,过度的拆分会加大阅读代码的难度。
同时,在封装类时,应将该抽象对象相关的属性或操作封装为类的实例属性和实例方法
。和该对象关系不是很紧密的属性或操作应考虑为其单独抽象一个类用于封装,并将该类设计为依赖于新类的关系。
2.OCP
开闭原则:
- 模块、类和函数应该对扩展开放,对修改关闭。 即如果有新功能,应该继承原有类来新增属性和方法来实现新需求。
- 通过继承和多态扩展来添加新功能。开闭原则是最重要的设计原则之一,是大多数设计模式的基础。
软件建设一个复杂的结构,当我们完成其中的一部分,就应该不要修改它,而是在其基础上继续建设
。
笔者认为,开闭原则想告诉我们的核心理念就是:若需要实现新的业务逻辑,应该尽量以新增代码片段的形式来实现 ,而非以在原代码段中新增或修改逻辑来实现
。道理很简单,代码中的逻辑是写死的固定逻辑,因此只要不去人为改动它,程序就会一直按照当前逻辑运行,即永不会出现问题(前提是当前的代码逻辑没有缺陷)。但只要人对原代码段进行了改动,那么代码就会有出现bug的风险(因为人不是机器,终究会犯错误)。
3.LSP
里氏替换原则: 1.在设计模块和类时,必须确保派生类型从行为的角度来看是可替代的 2.使用父类的地方都可以用子类替代
该原则提供给了我们以下几个设计思路:
- 父类最好为抽象类
- 子类可实现父类的非抽象方法,尽量不要覆盖重写已实现的方法
- 子类可写自身的方法,有自身的特性,在父类的基础上扩建
- 子类覆盖重写父类方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,后置条件(返回值)要更严格
- 对于这条原则,笔者认为
工程/项目的类型和性质
可能会影响该原则的实现方式:1.若正在编写工程/项目为一个
框架类型的软件工程,则此时在抽象对象时可以使用抽象类作为父类
。因为框架类型的项目本身就是在对一系列操作、接口以及逻辑进行规范化;而这也是抽象类所提供的功能之一:约束子类的行为。 2.若是实现某一块业务逻辑,则可以考虑不引入抽象类
。因为对于某些具体的业务进行抽象,可能不会派生出太多的子类。其次,这些类更多的可能是组合、依赖的形式相互配合使用,而非使用父类子类的关系来关联它们。
4.ISP
接口隔离原则:
- 减少了代码耦合,使软件更健壮,更易于维护和扩展
- 客户端不应该依赖它所不需要的接口
该原则指导我们在设计接口时,应按照最小原则设计接口内容。每一个接口只应实现单独的、小的功能需求,而不应将过多的功能封装到一个接口当中
。这样才能保证调用接口的调用端(客户端)才不会应该依赖它所不需要的接口(因为接口已经细化,调用端可以选择性的使用接口)。
5.DIP
依赖倒置原则:
- 高级模块不应该依赖低级模块,两者都应该依赖抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
- 依赖倒置中的倒置,指的是依赖关系的倒置(之前往往都是调用方依赖于被调用方);依赖倒置中的依赖指的是对象的依赖关系(之前往往都是调用方依赖的是实体)。可以通过以下几种方式来实现该原则:
1.
通过使用抽象类来固定/稳定住被调用方,调用方改为依赖于抽象类
。这样当每次需要新增功能时,只需要继承抽象类新增一个功能类,并实现其中指定的方法即可;调用方并不需要修改任何代码。 2.可使用依赖注入的方式来设计调用方
:被调用方的实例化是在外部,然后从外部传入到调用方内部来进行使用。
注意
: 在设计抽象类时,可使用接口隔离原则设计; 即按照功能需求可将不同的功能封装到多个抽象类中;实现新功能类时候继承多个抽象类即可。
该原则笔者认为是解耦的一大利器
。通过使用该原则,可以将本来为强耦合的类之间改为松耦合的关系,使得项目代码的可扩展性大大提升。同时,正确的依赖倒置应该是当该类所依赖的类无法正常工作时,该类可以通过依赖其他同类型的类继续运行(通过灵活更改依赖对象)或免受该问题的影响继续正常运行(若依赖的类不是用于当前类的主要逻辑)。即调用方可以灵活更换被调用方,被调用方的代码更改不会影响调用方的代码更改。
6.DRY 原则、KISS 原则、YAGNI 原则、LOD 法则
- DRY:不要干重复的事儿。
- KISS:不要干复杂的事儿,思从深而行从简。
- YAGNI:不要干不需要的事儿,尺度把握尤为重要,超越尺度则会有过度设计之嫌。
- LOD:最小依赖。
笔者这里通过一些自己的实际的编码实践来对一些原则进行解释:
当一段逻辑在代码中重复出现了3次以上的时候
,此时就应该考虑将该逻辑抽象为通用方法。当一个方法对应的代码段过于长时
,此时应该考虑先将方法内的逻辑进行分段,然后再针对每一段进行封装或添加相应的注释说明。我们需要尽量让复杂的问题简明化、简单化。尽量使用对象组合
,而不是继承来达到复用的目的;组合即类调用类,而非类继承类。- 即如果有大部分人都认为某一段代码有问题,则应该考虑重新设计那段代码的逻辑。
总结
笔者个人认为,这些原则所想表达的意图实际上是有一些共同点的。例如大部分的原则其实都在告诉我们:尽量不要去修改老代码。因为在你没有完全搞清楚老代码的结构时,盲目的去修改老代码可能会引起问题。通过合理的设计,使得每次在实现新需求时,都使用新增代码段并且使老代码依赖新代码或将两者组合应用的形式来实现新需求。这样即使新增编码出现问题,也只会影响新需求,而不会影响原有业务逻辑。 其次,在抽象业务需求时,应尽量将需求细化,将需求抽象为多个实体之间的关系。这样,在封装类时就不会出现某一个类的内容过于冗余,需求大部分相关的功能实现都集中在该类当中实现。这样,即使需求更改,也不会导致去更改大部分的类,只需更改对应点的类即可。