常言道理论是用来指导实践的,而理论又是通过实践不断检验和修正的结果,理论和实践就是这样相互促进,最后将一个领域推向新的高度。面向对象编程出现的半个多世纪里,设计原则就是在无数前辈的理论和实践中产生的。在我们日常开发中,会经常用到各种设计模式,设计原则就是这些设计模式的理论,而设计模式则是设计原则的实践。

        作为一名java程序员,我们都知道面向对象编程有三大特性,分别是封装、多态和继承。设计原则就是在这些特性的基础上进一步延伸和补充,让我们的代码更加健壮,具有较高的复用性和可扩展性,使得程序设计变成一门艺术。

1 单一职责

定义:不要存在多于一个导致类变更的原因

【设计原则】程序设计7大原则_父类

        单一职责顾名思义,就是一个框架、类、方法只负责一件事情,只能有一个能引起它变化原因。它的优势就是可以降低代码的复杂度,提高可读性和可维护性,使得同一模块的功能更加内聚。单一职责看来比较好理解,但实践起来可能会比较难,因为业务或者功能的拆分从来不是一件简单的事情,包括拆分的维度和粒度,这高度依赖于程序员的代码积累和业务素养。

2 开闭原则

定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是关闭的。

【设计原则】程序设计7大原则_设计原则_02

        开闭原则是面向对象最重要的设计原则,简单理解就是,我们写的代码不能因为需求的变化而变化,应该通过扩展的方式来适应变化。提倡一个类一旦开发完成,后续增加新功能就不应该通过修改这个类来完成,而应该通过继承,增加新的类。如果对原有的类进行修改,那么涉及到这个类的所有功能都有出错的风险,需要投入额外的测试资源,增加了代码的维护成本。

        开闭原则除了体现在代码的扩展性强之外,另外的潜台词是:控制需求变动风险,降低维护成本,毕竟在企业中,更看重的是维护成本。当然原来的类不是一定不能修改,如果对原来的代码很熟悉,涉及场景比较少、风险可控的情况下是可以修改的,或者代码面临重构,开闭原则就可以暂时放弃。

3 依赖倒置原则

定义:高层模块不应该依赖低层模块,两者都应该依赖于抽象。

【设计原则】程序设计7大原则_子类_03

        依赖倒置的中心思想是面向接口编程,话外的意思是相对于细节的多变性,抽象的东西要稳定的多。核心就是要依赖于抽象,不应该依赖于具体的实现,在java中的具体表现形式有以下几种:

  1. 模块间的依赖通过抽象发生,实现类之间不直接发生依赖关系,其依赖关系是通过接口或抽象类产生的;
  2. 接口或抽象类不依赖于实现类;
  3. 实现类依赖接口或抽象类。

        依赖倒置原则可以降低类之间的耦合性,提高系统稳定性,降低并行开发引发的风险,同时能提高代码的可读性和可维护性。

4 接口隔离原则

定义:用多个专门的接口,而不是使用单一的总接口,客户端不应该依赖他不需要的接口。

【设计原则】程序设计7大原则_父类_04

        接口隔离原则强调的是一个类对另一个类的依赖应该建立在最小的接口上,我们针对每一类业务要建立单独的接口,接口中的方法尽量少,而不需要复杂臃肿的接口,这也符合单一职责。例如DAO的接口,通常针对单表的操作是放在一个DAO中。

        接口隔离原则能够充分体现高内聚低耦合的设计思想,使得单个接口的设计灵活简单。但这里要注意,接口的拆分粒度要适中,过分细化也会导致系统设计的复杂化。

5 迪米特法则

定义:一个类对于其他类应该知道的越少越好,也就是一个对象对于其他对象了解的越少越好,只和朋友通讯,不和陌生人说话。

【设计原则】程序设计7大原则_父类_05

        迪米特法则又叫最少知道原则,初衷也是在于降低类之间的耦合性。这里提到了朋友的概念,那类和类的朋友关系包含哪些呢?其实就是关联和依赖的关系。具体包含:成员变量(关联)、方法的入参(依赖)、返回值(依赖)、方法内部实例化的对象(依赖)等,对于方法内部调用其他方法的返回值是不能称为朋友的。

        对于被依赖的类来说,无论内部逻辑多么复杂,都应该封装在类的内部,对外提供 public 方法,不泄露任何信息。所以迪米特法则能够体现封装的特性,也能降低类之间的耦合性。

6 里氏替换原则

定义:派生类(子类)在程序中替换其基类(超类)对象。

【设计原则】程序设计7大原则_子类_06

        里氏替换原则可以说是继承复用的基石,我们看一下继承带来的优缺点:

优点:

  • 提高代码的复用性,子类可以拥有父类的特征和行为;
  • 提高代码的扩展性,子类可以形似父类,也可以保留自我特征。

缺点:

  • 侵入性高,只要继承就有了父类的特征和行为,使得子类在一定程度上被约束,灵活性降低;
  • 增加了父子类的耦合性,如果父类中的变量或方法修改,就可能对子类的某些使用场景造成比较大的危害,同理,如果子类对父类中的方法重写也可能会造成未知的风险。

        可以说,继承包含了这样一层含义:父类中已经实现的方法,实际上是在约定规范和契约,虽然并不强制要求子类遵守这些规范,但是如果子类对这些已经实现的方法进行修改,就会破坏整个继承体系。里氏替换原则的出现就很好的对继承进行了约束,具体可以体现在以下几点:

  1. 子类必须实现父类的抽象方法,但不能重写(覆盖)父类非抽象(已实现)的方法;
  2. 子类可以增加自己特有的属性和方法;
  3. 子类重载父类的方法,方法的前置条件(即方法的入参)要比父类方法的入参更加宽松(否则父类的方法不会被执行);
  4. 子类重载父类的方法,方法的后置条件(即方法的返回值)要比父类的返回值更加严格或者相同。

​设计模式之里氏替换原则 - lovebeauty - 博客园​

        如果子类中确实要改变父类中已经实现的方法,那么建议断绝继承关系,改用关联或者依赖来实现,也就是下面将要说的合成复用原则。

7 合成复用原则

定义:尽量使用对象的组合/聚合,而不是继承关系达到软件复用的目的。

【设计原则】程序设计7大原则_父类_07

        合成复用原则又叫组合优于继承,通过里氏替换原则我们可以知道子类重写方法会破坏继承体系,引发风险,所以建议使用关联或者依赖关系实现。

        继承会破坏类之间的封装性,父类的内部细节暴露给子类,称之为白箱操作,而通过组合/聚合的方式,将已有对象纳入到新对象中,使得对象的功能可以被调用,这种行为称为黑箱操作。



最后简单谈一下对七大设计原则的理解:

  • 时刻考虑程序未来的“变化”,将变的东西独立出来,与稳定功能的代码分离;
  • 一切都是基于面向接口编程,而不是面向实现编程;
  • 实现模块之间的松耦合、模块内部的高内聚。