《设计模式之美》笔记:设计原则

SOLD原则

设计原则 缩写 解释 副作用 实操
单一职责原则 SRP 一个类或者模块只负责完成一个职责(或功能)。 细粒度太细会降低代码内聚,影响代码的可维护性。 先写一个粗粒度的类满足业务需求,根据实际需要持续重构。
开闭原则 OCP 添加一个新功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。 支持这一原则的关键是预留扩展点,但要小心过度设计。 只要改动不破坏原有的代码的正常运行,没有破坏原有的单元测试,我们就可以说这是一次合格的代码改动。
里氏替换原则 LSP 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。这作为一个设计原则,指导继承关系中子类该如何设计。 - 按照协议来设计,子类在设计的时候,要遵循父类的行为约定,包括函数声明实现的功能,对输入、输出、异常的约定,注释里特殊说明等,子类只能改变内部实现逻辑。
接口隔离原则 ISP 接口的调用者或者使用者不应该强迫依赖它不需要的接口。
依赖反转原则 DIP 高层模块不要依赖底层模块,高层模块和底层模块应该通过抽象来互相依赖。 实际上平常的业务代码开发中,高层模块依赖底层模块没有任何问题。这条原则主要用来指导框架层面的设计。


KISS原则 - 尽量保持简单。

  • 不要使用同事可能不懂的技术来实现代码。
  • 不要重复造轮子,要善于使用已经有的工具类库。
  • 不要过度优化。不要过度使用一些奇淫技巧(比如,位运算代替算术运算、复杂的条件语句代替if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。


YAGNI原则 - You Ain't Gonna Need it.

  • 不要去设计当前用不到的功能;不要去编写当前用不到的代码。核心思想就是:不要过度设计。
  • 比如,如果暂时只用Redis存储配置信息,以后可能会用到ZooKeeper。那么在未用到ZooKeeper之前,我们没必要提前编写这部分代码。但是,这并不是说我们不需要考虑代码的扩展性。我们还是要预留好扩展点,等到需要的时候,再去实现ZooKeeper存储配置信息这部分代码。


DRY原则 - Don't Repeat Yourself.

  • 区分三种典型代码重复情况:实现逻辑重复,功能语义重复,代码执行重复。
  • 有时,尽管代码的实现逻辑相同,但语义不同,那么它并不违反DRY原则。对于重复代码的问题,可以通过抽象成更细粒度函数的方式解决。(isValidUserName()跟isValidPassword()的例子)
  • 功能语义重复的情况,通常都违反了DRY原则。(isValidIp()跟checkIfIpValid()的例子)
  • 代码执行重复,也通常是违反DRY原则的,应该尽量优化。
  • 延伸:如何提高代码的复用性?
    • 减少代码耦合
    • 满足单一职责原则
    • 模块化
    • 业务与非业务逻辑分离
    • 通用代码下沉
    • 继承、多态、抽象、封装
    • 应用模板等设计模式
  • 辩证:实际上,除非有非常明确的复用需求,否则为了暂时用不到的复用需求,花费太多的时间、精力,投入太多的开发成本,并不是一个值得推荐的做法。也违反了YAGNI原则。


LOD原则(迪米特法则) - 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

  • 高内聚:以类举例,相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。
  • 松耦合:以类举例,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。
  • 辩证:设计原则本身没有对错,只有能否用对之说。不要为了应用设计原则而应用设计原则,我们在应用设计原则的时候,一定要具体问题具体分析。
    • 例子:序列化和反序列化是否应该分拆成两个接口?答案是看情况。
      • 如果Serialization类,只包含两个操作,没有太大必要分拆,否则就违背了高内聚思想。但如果Serialization类添加了更多功能,比如反序列化的函数变成了三个,一旦任一反序列化操作有代码改动,我们都需要检查测试所有依赖Serialization的代码是否还能正常工作。而其实部分使用者并没必要了解反序列化的“知识”,它们仅用到序列化。那么我们应该按照迪米特法则,将反序列化和序列化的功能隔离开来。

你可能感兴趣的:(《设计模式之美》笔记:设计原则)