软件设计原则和设计思想

警告
本文最后更新于 2021-08-09,文中内容可能已过时。

SOLID 原则,即 Single Responsibility Principle(单一职责原则)、Open-Closed Principle(开闭原则)、Liskov Substitution Principle(里式替换原则)、Interface Segregation Principle(接口隔离原则)、Dependency Inversion Principle(依赖倒置原则)。

1. 如何理解单一职责原则(SRP)?

单一职责原则是指一个类只负责完成一个特定的职责或任务,不能将多个无关的功能耦合在一个类中实现。这样设计可以提高代码的内聚性,减少代码的耦合性,增强代码的可读性、维护性、复用性等。

2. 如何判断类的职责是否足够单一?

在不同的场景和需求下,对类的职责是否单一可能有不同的判断结果。但一些指标可以帮助我们判断类是否满足单一职责原则,例如:

  • 类的方法、属性数量过多;
  • 类的职责与其他类的耦合度过高;
  • 类的私有方法过多;
  • 难以为该类取一个简洁、明确的名称等。

3. 类的职责是否设计得越单一越好?

单一职责原则通过避免将不相关的功能耦合在一起,提高类的内聚性,减少代码的耦合性来实现优秀的代码设计。然而,如果拆分过细,则会降低代码的内聚性,引入过多的类,反而会影响代码的可维护性。因此,在实践中,需要根据具体情况权衡利弊,争取达到职责单一且紧凑的代码设计。

1. 如何理解 “对扩展开放,对修改关闭”?

“对扩展开放,对修改关闭(Open-Closed Principle)”是指在设计软件模块的时候,应该使这个模块能够方便地进行扩展,而且不需要修改已有代码。这就要求我们在设计时,应该将可能发生变化或者扩展的部分抽象出来,形成稳定的接口或抽象类;而将具体的实现细节留给子类或者其他具体实现类去完成,在满足扩展性的同时,也保证了代码的稳定性。

2. 如何做到 “对扩展开放,对修改关闭”?

为了做到 “对扩展开放,对修改关闭”,我们需要注重抽象、多态和依赖倒置等原则的运用,通过定义稳定的接口或抽象类来限制具体实现,并隔离变化,使得改动最小,并且在新增需求的时候,尽量使用扩展的方式进行处理,即添加新的代码而不修改原有代码。

常用的方法包括良好的代码结构设计、基于接口而非实现编程、使用多态方式进行编写、采用依赖注入等方法。此外,还可以使用由“23种经典设计模式”的部分设计模式来实现扩展功能的添加,如装饰器模式、策略模式、模板方法模式、职责链模式、状态模式等。

LSP是一种面向对象设计的原则,指导如何在继承关系中设计子类,保证程序正确性及可扩展性。遵循LSP,可以使得代码更为健壮、易于维护和修改。

按照LSP的思想,“design by contract”,子类应该满足父类所定义的函数契约(或者叫协议)。也就是说,函数的功能声明、输入输出格式、异常等与父类相同,但内部实现逻辑可以不同。

理解LSP需要注意它与多态的区别。尽管从表面上看,它们很相似,都涉及继承体系的概念。但多态更着重于代码实现的灵活性,而LSP更注重设计的合理性和正确性。

因此,在使用LSP时需要保持思维的敏捷度,并始终遵循父子类的契约,以确保代码的正确功能和可靠性。

1. 如何理解“接口隔离原则”?

理解“接口隔离原则”的重点是理解其中的“接口”二字。这里有三种不同的理解。

若把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。当部分接口只被部分调用者使用时,则需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。

若把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要拆分函数成更细粒度的多个函数,以让调用者只依赖它需要的那个函数。

若把“接口”理解为面向对象编程语言中的接口语法,那接口的设计要尽量单一,不让接口的实现类和调用者依赖不需要的接口函数。

2. 接口隔离原则与单一职责原则的区别

单一职责原则针对的是模块、类和接口的设计。相对于单一职责原则,接口隔离原则更专注于接口的设计,它的思考角度也不同。接口隔离原则提供了一种判断接口职责是否单一的标准:通过调用者如何使用接口来判断。如果调用者只使用部分接口或接口的部分功能,则接口设计不够职责单一。

1. 控制反转

实际上,控制反转是一种比较抽象的设计思想,而不是一种具体的实现方法。它通常用于指导框架层面的设计。在这里,“控制”指的是程序执行流程的控制,“反转”则指在没有使用框架之前,由程序员自行控制整个程序的执行流程。但使用框架后,整个程序执行流程的控制权被反转给了框架本身。

2. 依赖注入

依赖注入与控制反转形成鲜明对比,它是一种具体的编码技巧。我们不会在类的内部使用 new 来创建依赖类的对象,而是通过构造函数、函数参数等方式在外部创建好依赖类的对象,再将其传递(或注入)给类来使用。

3. 依赖注入框架

借助依赖注入框架提供的扩展点,只需简单配置需要的所有类及它们之间的依赖关系,就能够使框架自动进行对象的创建、管理对象生命周期以及依赖注入等,这些原本需要开发人员手动处理的任务都得以由框架自动完成。

4. 依赖反转原则

依赖反转原则,也称为依赖倒置原则,与控制反转有些相似,主要用于指导框架层面的设计。它要求高层模块和低层模块共同依赖于同一个抽象,而不是低层模块依赖于高层模块。同时,抽象不能依赖于具体实现细节,具体实现细节应当依赖于抽象。这样可以降低高低层级直接的耦合度,增强代码的可扩展和维护性。

DRY 原则指的是 Don’t Repeat Yourself(不要重复自己)的设计原则。其目的在于避免代码中出现重复的逻辑或代码结构,提高代码可维护性和可复用性。

实现 DRY 的主要思路是减少代码耦合,满足单一职责原则,进行模块化设计,将业务与非业务逻辑分离,通用代码下沉,运用继承、多态、抽象、封装等面向对象编程的特性以提高代码的复用性。同时也可以采用应用模板等设计模式来优化代码的设计。

需要注意的是,即使实现逻辑重复但功能语义不重复的代码在一定程度上并不违反 DRY 原则。另外,代码执行重复也算是违反 DRY 原则,需要进行优化和重构。

综上所述,实践 DRY 原则需要综合考虑多个因素,包括代码结构、逻辑设计、面向对象编程思想等,并根据实际情况灵活选择合适的方法来提高代码可复用性和可维护性。

KISS 原则,即 Keep It Simple, Stupid(保持简单),避免过度设计或过度完成,尽可能采用简单的方法来解决问题。

  • 不要使用同事可能不懂的技术来实现代码;
  • 不要重复造轮子,要善于使用已经有的工具类库;
  • 不要过度优化。

YAGNI 原则的英文全称是:You Ain’t Gonna Need It。直译就是:你不会需要它。这条原则也算是万金油了。当用在软件开发中的时候,它的意思是:不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。

YAGNI 原则跟 KISS 原则并非一回事儿。KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。

LOD 原则,即 Law of Demeter(迪米特法则),避免代码之间产生不必要的耦合,要求一个对象对其他的对象有尽可能少的了解。

1. 如何理解“高内聚、松耦合”?

“高内聚、松耦合”是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。所谓松耦合指的是,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。

2. 如何理解“迪米特法则”?

不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。


设计模式之美