Unity 设计模式 之 【什么是设计模式】/ 【为什么要使用设计模式】/ 【架构和设计模式的区别】
目录
[Unity 设计模式 之 【什么是设计模式】/ 【为什么要使用设计模式】/ 【架构和设计模式的区别】](#Unity 设计模式 之 【什么是设计模式】/ 【为什么要使用设计模式】/ 【架构和设计模式的区别】)
[二、 Unity 设计模式](#二、 Unity 设计模式)
[1、Unity 开发中使用设计模式的特点](#1、Unity 开发中使用设计模式的特点)
[2、Unity 中常用的设计模式](#2、Unity 中常用的设计模式)
[3、Unity 开发中使用设计模式的优势](#3、Unity 开发中使用设计模式的优势)
[4、Unity 和其他环境使用设计模式的区别总结](#4、Unity 和其他环境使用设计模式的区别总结)
[1. 解决常见问题,提升开发效率](#1. 解决常见问题,提升开发效率)
[2. 促进代码的可维护性](#2. 促进代码的可维护性)
[3. 提高代码的可扩展性](#3. 提高代码的可扩展性)
[4. 增强代码的可复用性](#4. 增强代码的可复用性)
[5. 促进团队协作,改善沟通](#5. 促进团队协作,改善沟通)
[6. 降低系统的耦合性](#6. 降低系统的耦合性)
[7. 应对变化,提高系统的灵活性](#7. 应对变化,提高系统的灵活性)
[8. 避免代码重复](#8. 避免代码重复)
[五、设计模式 什么时候使用,怎么使用合适](#五、设计模式 什么时候使用,怎么使用合适)
[1. 何时使用设计模式](#1. 何时使用设计模式)
[1.1 当代码重复时](#1.1 当代码重复时)
[1.2 当系统需要扩展时](#1.2 当系统需要扩展时)
[1.3 当类和对象的依赖关系过于复杂时](#1.3 当类和对象的依赖关系过于复杂时)
[1.4 当需要应对变化时](#1.4 当需要应对变化时)
[1.5 当设计不符合SOLID原则时](#1.5 当设计不符合SOLID原则时)
[2. 如何合适地使用设计模式](#2. 如何合适地使用设计模式)
[2.1 识别需求](#2.1 识别需求)
[2.2 避免过度设计](#2.2 避免过度设计)
[2.3 遵循简单优先原则](#2.3 遵循简单优先原则)
[2.4 保持灵活性](#2.4 保持灵活性)
[2.5 组合使用设计模式](#2.5 组合使用设计模式)
[2.6 关注可测试性](#2.6 关注可测试性)
[2.7 学习经典案例](#2.7 学习经典案例)
[3. 常见设计模式的使用场景](#3. 常见设计模式的使用场景)
[3.1 单例模式(Singleton)](#3.1 单例模式(Singleton))
[3.2 工厂方法模式(Factory Method)](#3.2 工厂方法模式(Factory Method))
[3.3 观察者模式(Observer)](#3.3 观察者模式(Observer))
[3.4 策略模式(Strategy)](#3.4 策略模式(Strategy))
[3.5 装饰者模式(Decorator)](#3.5 装饰者模式(Decorator))
[六、23 种设计模式](#六、23 种设计模式)
一、简单介绍
设计模式 是指在软件开发中为解决常见问题而总结出的一套可复用的解决方案 。这些模式是经过长期实践证明有效的编程经验总结 ,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。
设计模式的特点:
- 通用性:设计模式针对的是软件开发中常见的设计问题,适用于各种软件工程项目。
- 可复用性:设计模式可以在不同项目和环境下被重复使用,提高代码的可维护性和扩展性。
- 可扩展性:设计模式有助于让代码结构更加灵活,易于扩展和修改。
- 模块化:通过设计模式,可以减少代码的耦合性,增强模块间的独立性。
- 提高沟通效率:设计模式为开发者提供了一种通用的设计语言,使得团队成员能够快速理解并讨论设计方案。
二、 Unity 设计模式
在 Unity 开发中使用设计模式 和在其他环境或平台上使用设计模式的核心原理是相同的。设计模式是软件设计中的通用解决方案,用于解决特定的设计问题,无论在哪个开发环境中,其本质都不变。然而,在 Unity 中使用设计模式与在其他开发平台上的应用有一些独特的差异和需要注意的地方,主要与 Unity 的游戏架构 和组件化设计相关。
1、Unity 开发中使用设计模式的特点
-
组件化架构和游戏对象:
- Unity 的核心设计是基于 GameObject-Component 体系 ,开发者通过将各种组件附加到游戏对象上来定义游戏逻辑。这种架构天然支持将代码功能分解成多个独立的模块,这和许多设计模式的目标非常一致,尤其是单一职责 和松耦合原则。
- 例如,在传统的面向对象编程中,继承 是常见的组织代码的方式,而在 Unity 中,组合 (Composition)更为普遍,开发者可以通过将不同的组件组合到一个游戏对象上,创建丰富的行为。这和某些设计模式(如策略模式)的思想相近,策略模式鼓励通过组合不同策略来改变对象行为,而不是通过继承。
-
Unity 的生命周期与 MonoBehaviour:
- MonoBehaviour 是 Unity 中的基础脚本类,许多脚本类通过继承它来实现游戏逻辑。MonoBehaviour 提供了一组生命周期方法,如
Start()
、Update()
、OnTriggerEnter()
等,用来管理脚本的执行顺序。 - 在 Unity 中使用设计模式时,必须考虑 Unity 的这种生命周期管理。例如,单例模式 在其他环境下可能涉及懒加载或复杂的初始化逻辑,而在 Unity 中,单例对象通常会与
DontDestroyOnLoad
方法结合,确保它在场景切换时不被销毁。
- MonoBehaviour 是 Unity 中的基础脚本类,许多脚本类通过继承它来实现游戏逻辑。MonoBehaviour 提供了一组生命周期方法,如
-
游戏开发的实时性:
- 实时性 是游戏开发中的关键因素,Unity 开发中往往需要处理大量实时的输入输出、物理运算、渲染 等。在这种环境下,设计模式的使用需要考虑到性能开销。例如,在使用观察者模式时,观察者的数量和频率可能会影响游戏的帧率,所以需要特别小心管理订阅与通知的机制。
-
脚本与引擎的紧密结合:
- 在 Unity 中,脚本和引擎的底层功能紧密结合,例如物理引擎、渲染系统、动画系统等。因此,设计模式在使用时通常需要与 Unity 提供的 API 协同工作。
- 例如,在实现工厂方法模式 时,开发者可能需要通过 Unity 的
Instantiate()
方法动态创建游戏对象,而不是直接使用面向对象编程中的new
操作符。这意味着在 Unity 中使用某些设计模式时,需要借助 Unity 的特定工具和功能。
2、Unity 中常用的设计模式
-
单例模式 (Singleton)
- 应用场景 :用于全局管理器类,如
GameManager
、AudioManager
,确保全局状态在整个游戏生命周期中唯一且可访问。 - Unity 特点 :通常结合
DontDestroyOnLoad()
方法来保证在场景切换时,单例对象不会被销毁。
- 应用场景 :用于全局管理器类,如
-
工厂方法模式 (Factory Method)
- 应用场景:用于动态生成游戏对象,如敌人、道具等。通过工厂方法,可以根据游戏逻辑在不同时间点生成不同类型的对象。
- Unity 特点 :通常结合
Instantiate()
方法来生成对象的实例,并从资源中加载预制件(Prefab)。
-
观察者模式 (Observer)
- 应用场景:用于处理事件机制,如 UI 按钮点击、游戏中角色生命值变化、AI 事件等。
- Unity 特点 :可以使用 Unity 的事件系统(如
UnityEvent
),或自行管理观察者列表。
-
状态模式 (State)
- 应用场景:用于控制对象的状态切换,尤其是在角色动画、AI 行为树等场景中非常有用。
- Unity 特点 :可以结合 Unity 的
Animator
状态机,管理复杂的状态切换。
-
策略模式 (Strategy)
- 应用场景:用于动态改变对象行为,如不同武器或攻击方式、不同移动方式等。
- Unity 特点:可以通过将不同的行为封装为独立的脚本组件,并根据需要动态添加或移除组件,来实现灵活的行为切换。
-
命令模式 (Command)
- 应用场景:用于实现撤销操作、输入控制器、行为记录等。
- Unity 特点:可以结合 Unity 的输入系统来实现一系列命令的执行和撤销操作。
3、Unity 开发中使用设计模式的优势
-
提高代码复用性:设计模式有助于将复杂的功能拆解为可复用的组件。例如,通过工厂模式和预制件,开发者可以动态生成各种对象,而不必重复编写对象初始化代码。
-
增强代码的可维护性和扩展性:例如,通过状态模式管理角色状态,使得后期修改或添加新的状态变得更加容易,而不需要重写或修改大量代码。
-
符合 Unity 组件化设计:Unity 强调通过组件组合实现复杂功能,这与很多设计模式(如策略模式、装饰模式)的思路不谋而合。通过使用这些设计模式,开发者能够更加灵活地组织代码,使其更符合 Unity 的框架结构。
-
减少耦合性:设计模式如观察者模式可以减少不同模块之间的耦合,使代码结构更为松散,方便以后扩展或维护。
4、Unity 和其他环境使用设计模式的区别总结
|----------------|-------------------------------------------|---------------------------------|
| 特性 | Unity 中使用设计模式 | 其他开发平台中使用设计模式 |
| 组件化架构 | 基于 GameObject 和 Component 体系,倾向于组合而非继承 | 传统面向对象编程中常使用继承,较少采用组合 |
| 生命周期管理 | 受 MonoBehaviour 生命周期的影响,设计模式可能需要适应这种结构 | 自由管理对象的创建与销毁,生命周期不依赖于引擎 |
| 实时性需求 | 游戏开发中需要高效管理设计模式,避免性能问题 | 其他系统中,实时性要求相对较低,设计模式对性能影响较小 |
| 引擎和 API 交互 | 需要结合 Unity API,如 Instantiate()、Animator 等 | 大多数情况下依赖于标准库和 API,自由度较高 |
| 场景切换和数据持久化 | 设计模式需要考虑跨场景的数据管理,如使用 DontDestroyOnLoad() | 常规应用中,数据管理不需要考虑场景切换,通常依赖数据库等持久化 |
总之,Unity 中的设计模式应用与传统开发环境有一些区别,主要体现在 Unity 的组件化架构 和生命周期管理上。开发者需要根据 Unity 的独特特性来调整设计模式的实现方式,以便更好地与 Unity 引擎协同工作。通过合理使用设计模式,开发者可以创建更具扩展性、可维护性和复用性的代码,同时提高开发效率和代码质量。
三、什么是软件框架,软件框架和设计模式的区别
软件框架 是一个 可复用的代码结构或库 ,它为软件开发提供了一个 基本的骨架和通用功能,开发人员可以在此基础上进行扩展和定制,以满足特定应用的需求。框架通常提供一组标准化的接口、类和工具,帮助开发者快速开发应用程序,而不需要从零开始编写底层代码。
软件框架的特点:
- 可复用性:框架提供了大量可复用的组件和模块,使开发者无需重复开发常见功能。
- 约定优于配置:框架通常包含默认的约定和最佳实践,开发者只需遵循框架的约定,便可避免大量的配置工作。
- 控制反转(Inversion of Control, IoC) :在框架中,控制权通常从开发者转移到框架。框架会自动管理应用程序的流程和生命周期,开发者只需在特定点扩展代码。
- 简化开发流程:通过提供常见功能的实现,如数据库交互、UI 渲染、网络通信等,框架简化了开发流程,提高了开发效率。
- 扩展性:开发者可以在框架的基础上扩展功能,框架通常允许定制和扩展特定模块。
设计模式:
设计模式是通用的、可复用的解决方案 ,用于解决软件设计中的特定问题。设计模式是一种高层次的设计思路,是对编程中常见问题的总结和抽象。
软件框架和设计模式的区别:
|----------------|-------------------------------------------|---------------------------------|
| 特性 | Unity 中使用设计模式 | 其他开发平台中使用设计模式 |
| 组件化架构 | 基于 GameObject 和 Component 体系,倾向于组合而非继承 | 传统面向对象编程中常使用继承,较少采用组合 |
| 生命周期管理 | 受 MonoBehaviour 生命周期的影响,设计模式可能需要适应这种结构 | 自由管理对象的创建与销毁,生命周期不依赖于引擎 |
| 实时性需求 | 游戏开发中需要高效管理设计模式,避免性能问题 | 其他系统中,实时性要求相对较低,设计模式对性能影响较小 |
| 引擎和 API 交互 | 需要结合 Unity API,如 Instantiate()、Animator 等 | 大多数情况下依赖于标准库和 API,自由度较高 |
| 场景切换和数据持久化 | 设计模式需要考虑跨场景的数据管理,如使用 DontDestroyOnLoad() | 常规应用中,数据管理不需要考虑场景切换,通常依赖数据库等持久化 |
总之,两者之间的主要区别在于,框架 是一种可以直接使用的工具和代码库,而设计模式则是对软件设计的经验总结,提供了一种组织代码的模式和思路。
- 软件框架 是更具 实际性的工具,它为开发者提供了代码实现和通用功能,可以直接使用,通常用于开发某一类型的应用程序。
- 设计模式 是一种理论性的设计方案,用于指导如何设计代码结构,解决开发中反复出现的问题。设计模式强调的是设计原则和结构,不提供直接的代码实现。
四、开发设计中为什么要使用设计模式
在软件开发中使用设计模式的主要目的是为了提高代码的可维护性 、可扩展性 和 复用性。设计模式提供了经过验证的、可重复使用的解决方案,帮助开发人员避免常见的设计问题,简化开发过程。具体来说,使用设计模式有以下几个主要原因:
1. 解决常见问题,提升开发效率
设计模式是经过实践检验的通用解决方案,针对常见的设计挑战提供了标准化的方法。通过应用这些模式,开发人员不必从零开始设计解决方案,可以直接借用已有的模式来解决类似问题,从而提高开发效率。
2. 促进代码的可维护性
设计模式帮助开发人员构建更加清晰的代码结构,减少复杂性,方便后期的维护和修改。例如,使用单例模式 可以确保系统中的某个类只有一个实例,避免了全局状态混乱,便于管理。而观察者模式则可以帮助系统模块之间松散耦合,易于扩展和维护。
3. 提高代码的可扩展性
设计模式鼓励使用接口和抽象类来解耦模块,使系统的各个部分独立演化。例如,工厂方法模式 和抽象工厂模式通过创建对象的接口,将实例化对象的具体类与使用这些对象的代码分离,允许轻松扩展或更改系统功能,而不破坏现有代码。
4. 增强代码的可复用性
设计模式使代码具备更高的复用性。许多设计模式通过封装变化的部分,使得代码可以在不同场景下复用。比如,策略模式将算法和具体实现分离,可以动态替换不同的算法,而无需修改客户端代码,从而增强了代码的复用能力。
5. 促进团队协作,改善沟通
设计模式是一种通用的设计语言,使用设计模式可以促进团队成员之间的沟通。不同的开发者在讨论系统设计时,通过提及具体的设计模式(如代理模式 或装饰者模式),能够快速理解彼此的设计意图,减少沟通障碍。
6. 降低系统的耦合性
设计模式通过将模块之间的交互松散耦合,降低了系统各部分之间的依赖性。例如,观察者模式可以实现模块之间的松散耦合,使得一个模块的变化不会影响其他模块,从而提高系统的灵活性和可扩展性。
7. 应对变化,提高系统的灵活性
设计模式帮助开发人员构建适应变化的系统。软件开发中,需求经常发生变化,设计模式通过提供灵活的结构,使得系统可以更容易地适应这些变化。例如,状态模式 允许对象在状态变化时切换行为,避免了大量的
if-else
语句。8. 避免代码重复
设计模式提供了标准的解决方案,可以避免重复实现相同功能的代码。例如,模板方法模式定义了算法的骨架,允许子类重写某些步骤,而无需重复整个算法的实现,减少了代码重复,提高了代码的一致性。
总之,使用设计模式不仅能够解决常见的设计问题,还可以提升代码的可读性、可维护性和扩展性,帮助开发人员创建灵活、易于修改的系统。同时,它们作为一种通用的开发语言,能促进团队协作和沟通,使开发工作更加高效。
五、设计模式 什么时候使用,怎么使用合适
使用设计模式 的关键在于解决特定的软件设计问题,而不只是为使用而使用。选择适当的设计模式可以提高代码的质量和开发效率。要知道什么时候使用和如何合适地使用设计模式,必须基于问题的性质、软件的复杂度、团队协作需求等。以下是一些原则和使用建议:
1. 何时使用设计模式
1.1 当代码重复时
当你发现系统中存在重复代码时,可以考虑使用设计模式来减少重复。例如,如果多个地方都在实现相似的对象创建逻辑,你可以使用工厂方法模式来统一对象的创建逻辑。
1.2 当系统需要扩展时
如果系统在未来可能需要扩展,那么设计模式可以帮助构建灵活的架构。例如,策略模式 允许你轻松添加新的算法或操作,而不修改现有代码;装饰者模式允许你动态扩展对象的功能,而不影响其他对象。
1.3 当类和对象的依赖关系过于复杂时
当你发现类之间的依赖关系过于紧密,难以维护或扩展时,设计模式可以帮助解耦。比如,观察者模式 可以使类之间松散耦合,避免直接依赖;中介者模式可以集中管理对象间的交互,减少直接的依赖。
1.4 当需要应对变化时
如果你预见到系统某些部分会频繁变化,而其他部分则相对稳定,那么可以使用设计模式来隔离变化。例如,状态模式 适用于当对象状态变化时需要改变其行为的场景,避免了硬编码多个
if-else
语句。1.5 当设计不符合SOLID原则时
设计模式能帮助你遵循SOLID原则(单一职责、开闭原则、里氏替换、接口分离、依赖倒置)。例如,依赖倒置原则 可以通过使用依赖注入 和抽象工厂模式来实现,减少高层模块对低层模块的依赖。
2. 如何合适地使用设计模式
2.1 识别需求
首先,清楚理解问题或需求。不要盲目使用设计模式,除非它能解决特定的问题。每种设计模式都有其特定的应用场景。例如:
- 如果你的系统需要支持不同的产品创建过程,使用工厂模式。
- 如果你需要为一个对象动态地添加行为,可以考虑装饰者模式。
- 如果你的对象需要根据不同的状态表现出不同的行为,使用状态模式。
2.2 避免过度设计
设计模式的使用应当有针对性。过度使用设计模式可能会使系统复杂化,增加代码的阅读和维护成本。仅在需要时使用设计模式,不要为了炫技或追求模式而引入不必要的复杂性。例如,单例模式虽然简单,但如果过度使用,可能导致全局状态共享,增加调试难度。
2.3 遵循简单优先原则
始终优先选择简单而清晰的设计方案。如果某个问题可以通过简单的继承或组合来解决,那么没有必要引入复杂的设计模式。设计模式是解决复杂问题的工具,但不要让模式增加不必要的复杂性。
2.4 保持灵活性
使用设计模式时要考虑到系统的未来扩展。设计模式应当是灵活的,避免硬编码逻辑。例如,策略模式允许你动态替换算法,而不是写死逻辑,这样可以灵活地适应变化的需求。
2.5 组合使用设计模式
在复杂的系统中,有时需要组合多种设计模式来解决问题。例如,工厂模式 可以与单例模式 组合使用,确保创建的对象是单例的;或者将观察者模式 和中介者模式结合使用,管理多个观察者的交互。
2.6 关注可测试性
设计模式还能提升代码的可测试性。通过使用依赖注入 或工厂方法模式,可以让代码依赖于接口而不是具体实现,从而更容易进行单元测试和替换依赖。
2.7 学习经典案例
学习常见的设计模式应用场景和经典案例,尤其是在已有的框架或库中。例如,Java 中的
java.util.Observer
接口实现了观察者模式,Spring 框架中广泛使用的依赖注入机制基于依赖倒置原则和工厂模式。3. 常见设计模式的使用场景
3.1 单例模式(Singleton)
- 使用场景:确保某个类只有一个实例,例如数据库连接池或配置管理类。
- 如何使用:控制类的实例化,提供全局访问点。
3.2 工厂方法模式(Factory Method)
- 使用场景:当需要创建对象但不想显式指定对象的类时。
- 如何使用:定义一个用于创建对象的接口,将具体的对象创建工作推迟到子类。
3.3 观察者模式(Observer)
- 使用场景:当一个对象状态发生变化时需要通知其他对象,如事件监听系统。
- 如何使用:定义一对多的依赖关系,一个对象状态变化时通知所有依赖对象。
3.4 策略模式(Strategy)
- 使用场景:当有多种算法可以替换且需要动态选择时,例如支付方式选择。
- 如何使用:定义一组算法,将它们封装在独立的类中,并通过接口进行互换。
3.5 装饰者模式(Decorator)
- 使用场景:需要动态地给对象增加功能,而不影响其他对象时。
- 如何使用:用一个装饰对象包裹真实对象,在不改变对象结构的情况下增加新功能。
总之,使用设计模式的关键在于正确理解问题的性质,结合设计模式的应用场景来做出合适的选择。要避免过度设计,保持代码简单,同时确保系统的灵活性和可维护性。掌握常见设计模式及其组合,能够帮助你应对复杂的软件设计问题,提升代码质量。
六、23 种设计模式
1、创建型模式(5种)
- 工厂方法模式(Factory Method):定义一个接口用于创建对象,但让子类决定实例化哪一个类。
- 抽象工厂模式(Abstract Factory):提供一个创建相关或依赖对象的接口,而不指定具体类。
- 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
- 建造者模式(Builder):将对象的构建与其表示分离,以便相同的构建过程可以创建不同的对象。
- 原型模式(Prototype):通过复制现有的实例来生成新对象,而不是通过实例化类。
2、结构型模式(7种)
- 适配器模式(Adapter):将一个类的接口转换为客户希望的另一个接口,使原本不兼容的类可以一起工作。
- 桥接模式(Bridge):将抽象部分与它的实现部分分离,以使它们可以独立变化。
- 装饰者模式(Decorator):动态地给一个对象添加新的职责,避免了创建子类的需要。
- 组合模式(Composite):将对象组合成树形结构以表示部分-整体的层次结构,客户可以同样地对待单个对象和组合对象。
- 外观模式(Facade):为子系统中的一组接口提供一个统一的接口,使得子系统更容易使用。
- 享元模式(Flyweight):通过共享细粒度的对象来减少内存使用。
- 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
3、行为型模式(11种)
- 模板方法模式(Template Method):在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中。
- 命令模式(Command):将请求封装为对象,从而可以用不同的请求对客户进行参数化、排队或记录请求日志,以及支持可撤销操作。
- 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
- 观察者模式(Observer):定义对象间的一对多依赖,当一个对象改变状态时,其依赖者都会得到通知并自动更新。
- 中介者模式(Mediator):定义一个对象来封装一系列对象之间的交互,中介者使各对象不需要显式地相互引用,从而使它们之间的耦合松散,且可以独立地改变它们之间的交互。
- 备忘录模式(Memento):在不破坏封装性的前提下,捕获对象的内部状态,并在以后恢复该状态。
- 解释器模式(Interpreter):为某个语言定义一个语法表示,并定义一个解释器,使用该表示来解释语言中的句子。
- 状态模式(State):允许对象在内部状态改变时改变它的行为,看起来对象好像修改了它的类。
- 策略模式(Strategy):定义一系列算法,将每一个算法封装起来,并让它们可以互换,策略模式使得算法可以独立于使用它的客户而变化。
- 职责链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
- 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
在日常应用中,设计模式从来都不是单个设计模式独立使用的。在实际应用中,通常多个设计模式混合使用,你中有我,我中有你。下图完整地描述了设计模式之间的混用关系,希望对大家有所帮助。
七、七大设计原则(SOLID)
设计模式中的设计原则是软件设计的核心思想,旨在帮助开发人员创建更具可维护性 、可扩展性 和灵活性 的代码结构。这些原则是设计模式的基础,指导我们如何编写高内聚、低耦合的代码。以下是设计模式中常见的设计原则:
1. 单一职责原则 (Single Responsibility Principle, SRP)
- 定义:一个类只负责一项职责,应该只有一个引起它变化的原因。
- 解释:每个类都应该有且只有一个功能或行为。如果一个类承担了过多的责任,修改一个功能可能会影响到其他功能,增加维护的复杂性。
- 实例:假设有一个类既负责文件的保存,又负责文件内容的格式化。这时应将它拆分为两个类:一个负责文件保存,一个负责格式化。
2. 开闭原则 (Open/Closed Principle, OCP)
- 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
- 解释 :当需求发生变化时,应该通过扩展 类的行为来应对新需求,而不是修改已有的代码。这减少了对现有代码的影响,避免引入新 bug。
- 实例:在处理不同类型的支付时,添加新的支付方式应该通过继承扩展,而不是修改现有的支付处理逻辑。
3. 里氏替换原则 (Liskov Substitution Principle, LSP)
- 定义:所有引用基类的地方必须能透明地使用其子类对象。
- 解释:子类应该能够替换其基类,而不影响程序的正确性。换句话说,子类应该完全遵循基类的行为规范,不能违背基类的约定。
- 实例 :如果基类
Animal
有一个方法makeSound()
,而Dog
和Cat
类继承了它,那么无论是Dog
还是Cat
对象,都应该能够被用作Animal
类型,而不影响程序功能。4. 依赖倒置原则 (Dependency Inversion Principle, DIP)
- 定义:高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
- 解释:在设计中,应该尽量依赖抽象(接口或抽象类),而不是具体实现。这样可以减少类之间的耦合,使代码更加灵活。
- 实例 :一个高层模块
PaymentService
应该依赖于IPaymentProcessor
接口,而不是具体的PaypalProcessor
类。这样可以轻松替换不同的支付处理方式,而不需要修改PaymentService
的代码。5. 接口隔离原则 (Interface Segregation Principle, ISP)
- 定义:客户端不应该依赖于它不需要的接口。
- 解释:应将大接口拆分为多个小接口,每个接口只包含客户端所需的方法。这样避免客户端实现冗余的接口方法,减少不必要的依赖。
- 实例 :如果一个接口包含了太多职责,比如
IWorker
接口既有work()
方法又有eat()
方法,那么不相关的类会被迫实现不需要的方法。应该将IWorker
接口分为IWorkable
和IEatable
,分别定义不同职责的接口。6. 迪米特法则 (Law of Demeter, LoD)
- 定义:一个对象应该对其他对象有最少的了解。
- 解释 :又称最少知识原则,它强调对象之间的低耦合。一个对象不应该直接操作或依赖于其他类的内部细节,只应该通过其直接依赖的对象进行通信。
- 实例 :假设有一个类
Car
,它有一个Engine
对象。在设计时,Car
应该只通过Engine
的公共接口与Engine
交互,而不是深入访问Engine
内部的子对象。7. 合成复用原则 (Composition Over Inheritance)
- 定义:尽量使用组合(对象包含关系)来实现功能,而不是通过继承。
- 解释:通过组合,将类的功能封装成独立的组件,避免继承带来的层级复杂性和继承耦合问题。组合可以实现更灵活的对象行为扩展,而继承会造成类的高度依赖。
- 实例 :假设你有一个类
Bird
,而想创建会游泳的鸟类,应该使用一个Swimming
组件来添加游泳功能,而不是通过继承多个类来实现(如SwimmingBird
)。
|------------|---------------------------------------------------------------------------------------------------------------|
| 开闭原则 | 面对需求,对程序的改动是通过增加新代码进行的,而不是改变原来的标题 |
| 依赖倒转原则 | 高层模块不应该依赖底层模块,两个都应该依赖与抽象;抽象不应该依赖于细节,细节应该依赖于抽象。所以要针对接口编程,不要针对实现编程。 |
| 里氏代换原则 | 由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象 |
| 单一职责原则 | 一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类,就一个类而言,应该仅有一个引起它变化的原因 |
| 接口隔离原则 | 接口隔离原则是指使用多个专门的接口,而不使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干 |
| 合成复用原则 | 合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承 |
| 迪米特法则 | 一个软件实体应当尽可能少的与其他实体发生相互作用。在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限 |
[七大设计原则]