[TOC]

SOLID原则包括单一职责,开闭原则,里氏替换原则,接口隔离原则,依赖倒置原则。

单一职责原则(SRP)

SRP: Single Responsibility Principle

A class or module should have a single reponsilibility.一个类或者模块只负责完成一个职责(或者功能)

一个类只负责完成一个职责或功能, 也就是说, 不要设计大而全的类, 要设计粒度小, 功能单一的类.

换个角度来讲就是. 一个类包含了两个或两个以上业务不相干的功能, 我们就说它的职责不够单一, 应该将它拆分成多个功能更加单一, 粒度更细的类.

如何判断类是否单一

  1. 类中的代码行数,函数或者属性过多.
  2. 类依赖的其它类过多, 或者依赖类的其它类过多, 不符合高内聚, 低耦合的设计思想.
  3. 私有方法过多
  4. 比较难给类起一个合适的名字
  5. 类中大量的方法都是集中操作类中的某几个属性.

如何设计单一类

技巧: 持续重构. 没有一尘不变的设计

单一职责原则通过避免设计大而全的类, 避免将不相关的功能耦合在一起, 来提高类的内聚性. 同时类职责单一, 类依赖和被依赖的其它类也会变少, 减少了代码的耦合性, 以此来实现代码的高内聚, 低耦合.

但是, 如果拆分得过细, 实际上会适得其反, 反倒会降低内聚性, 也会影响代码的可维护性.

开闭原则(OCP)

OCP: Open Closed Priciple

Software entities (modules, classes, functions, etc.) should be open for extension, but closed for modification.

软件实体(模块, 类, 方法等) 应该对扩展开放, 对修改关闭

扩展性是代码质量最重要的衡量标准之一. 在23种经典设计模式中, 大部门设计模式都是为了解决代码的扩展性问题而存在的, 主要遵从的设计原则就是开闭原则

对扩展开放,对修改关闭

添加一个新的功能,应该是通过已用的代码基础上扩展代码(新增模块, 类, 方法, 属性等), 而非修改已有代码(修改模块, 类, 方法, 属性等)的方式来完成.

  1. 开闭原则则并不是说完全杜绝修改, 而是以最小的修改代码的代价来完成新功能的开发
  2. 同样的代码改动, 在粗代码粒度下, 可能被认定为"修改"; 在细代码粒度下, 可能又被认定为"扩展"

提高代码扩展性的方法

底层逻辑: 对扩展开放, 对修改关闭

  1. 多态
  2. 依赖注入
  3. 基于接口而非实现编程
  4. 设计模式
    1. 装饰器模式
    2. 模板模式
    3. 职责链模式
    4. 状态模式
    5. 等等

如何灵活应用开闭原则

唯一不变的只有变化本身

开发一般分为

  1. 业务导向系统

    1. 金融系统, 电商系统, 物流系统等.
    2. 分析:
      1. 识别出尽可能多的扩展点, 对业务有足够的了解.
      2. 能够知道当及未来可能要支持的业务需求.
  2. 通用,偏底层的系统

    1. 如框架, 组件, 类库等

    2. 分析

      1. 会被如何使用?

      2. 今后打算添加哪些功能?

      3. 未来会有哪些更多的功能需求?

Tip: 扩展性是有代价的, 会牺牲可读性. 我们需要在扩展性与可读性之间做平衡.

  1. 某些场景下,代码的扩展性很重要, 可以适当牺牲一些代码的可读性.
  2. 另一些场景下, 代码的可读性更加重要, 可以适当牺牲一些代码的可扩展性.

为什么要做开闭原则

对扩展开放是为了应对变化(需求), 对修改关闭是为了保证已用代码的稳定性, 最终是为了让系统更有弹性.

很多设计原则, 设计思想, 设计模式都是以提高代码的扩展性为最终目的的

里氏替换原则(LSP)

LSP: Liskov Substitution Principle

Barbara Liskov:

If S is a subtype of T, then object of type T may be replaced with objects of type S, without breaking the program

如果S是T的子类型,则类型T的对象可以替换为类型S的对象,而不会破坏程序

Robert Martin:

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.

使用基类引用指针的函数必须能够在不知情的情况下使用派生类的对象。

综合两者的描述:子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

一种更接地气的描述: Design By Contract. 按照协议来设计

违反里氏替换原则

  1. 子类违背父类声明要实现的功能。
  2. 子类违背父类对输入,输出,异常的约定。
  3. 子类违背父类注释中所罗列的任何特殊说明

遵守协议,保证一致性,即符合里氏替换原则。

tip: 小窍门,拿父类的单元测试去验证子类代码,验证里否符合里氏替换原则。

接口隔离原则(ISP)

ISP: Interface Segregation Principle

Clients should not be forced to depend upon interface that they do not use.

客户端不应该强迫依赖它不需要的接口。”客户端“可理解为接口的调用者或使用者。

接口可以理解为三种不同情况下的接口概念。

  1. 一组 API 接口集合
  2. 单个 API 接口或函数
  3. OOP 中的接口概念

一组 API 接口集合

如设计微服务或类库接口的时候,如果部分只被部分调用者使用,那我们就需要将这部分接口隔离出来,单独给对应的调用者使用,而不是强迫其他调用者也依赖这部分不会被使用到的接口。

tip: 使用者需要几个接口就提供几个接口,不提供不使用的接口。

单个 API 接口或函数

把接口理解为单个接口或函数。那接口隔离原则就可以理解为:函数的设计要功能单一,不要将多个不同的功能逻辑在一个函数中实现。

tip: 一个函数只实现一种逻辑的代码。

与单一原则的区别:单一职责则针对是模块,类,接口的设计。

OOP 中的接口概念

理解为面向对象的接口语法。则接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

依赖倒置原则(DIP)

控制反转(IOC)

Inversion Of Control

  1. “控制”是指对程序执行流程的控制

  2. ”反转“ 指没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员”反转“到了框架。

tip: 控制反转是指对程序执行流程的控制权交给框架完成。

Go代码实现控制反转实例代码

依赖注入(DI)

Dependency Injection

不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数,函数参数等方式传递(或注入)给类使用。

tip: 基于接口而非实现编程

Go代码实现依赖注入实例代码

依赖反转原则(DIP)

Dependency Inversion Principly

  1. 高层模块不要依赖低层模块。
  2. 高层模块和低层模块应该通过抽象来互相依赖。
  3. 除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象

tip: 都是基于开闭原则,提高代码扩展性。

big-white