读书笔记----软件设计原则、设计模式

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/homework/11833
这个作业的目标 理解软件设计原则和设计模式

阅读《游戏编程模式》与《设计模式》笔记

《游戏编程模式》主要介绍了游戏开发中常用的四大类型,19个设计模式的用法,使用场景

设计原则

LSP:里氏代换原则

Liskov Substitution Principle,LSP

  • 若在任何情况下,子类或实现类与基类都可以互换,那么继承的使用是合适的
  • 子类不能添加任何父类没有的附加约束
  • 子类对象必须可以替换基类对象

例子

正方形继承长方形类,违背LSP原则,导致出现错误。

OCP:开闭原则

Open Closed Principle,OCP

软件实体应该是可扩展的,但不可修改的

  • 对于扩展是开放的
  • 对于更改是封闭的
    • 对模块行为扩展时,不必改动模块源代码或二进制代码

OCP的关键在于抽象

  • 抽象技术:abstract class,Interface
  • 抽象预见了可能的所以扩展(闭)
  • 抽象随时可导出新的类(开 )

例子:

手开关抽屉,门,冰箱。。。

那如果手直接和可开关的具体类关联,则每次添加新的具体类都需要修改手的源代码

解决方案:新增一个接口

SRP:单一职责原则

Single Responsibility Principle,SRP

  • SRP体现了内聚性,即一个模块的组成元素直接的功能相关性
  • 类的职责定义为“变化的原因”,当一个类承担了多于一个职责,那么引起变化的原因会有多个

例子

解决方案

ISP:接口隔离原则

Interface Segregation Principle,ISP

  • 客户不应该依赖他们用不到的方法,只给每个用户它所需要的接口
  • 为避免肥接口,应该一个类实现多个接口,每个客户仅知道必须的接口
  • 要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

例子:接口污染

DIP:依赖倒置原则

Dependence Inversion Principle,DIP

  • 高层模块不应该依赖底层模块,二者应该依赖于抽象
  • 抽象不应该依赖于细节,细节应该依赖于抽象
  • 针对接口编程,不要针对实现编程

例子:传统依赖关系

解决方案

LKP:最少知识原则

Least Knowledge Principle,又叫迪米特法则(Law of Demeter,LoD)

启发式原则

  • 依赖于抽象,即所有依赖关系都应该止于抽象类或接口
  • 任何类不应该从具体类派生
  • 任何变量不应该拥有指向具体类的指针或者引用
  • 任何方法都不应该改写任何基类中已经实现的方法

通用

外观模式

引入外观角色,为子系统的访问提供简单而单一的接口,是“迪米特法则”的体现

降低原有系统的复杂度,同时降低客户类与子系统类的耦合度,提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节

代理模式

原有类无法做的事情,交给代理类执行

例子:模拟实现MonoBehavior的帧更新和协程

装饰器模式

继承原有类,不改变原有接口,扩展新功能

适配器模式

保留现有类所提供的服务,改变原有接口,从而适配用户需求

两种适配方式

  • 对象适配器:包含原有类
  • 类适配器:继承原有类

装饰器模式和适配器模式都不改变原有类的代码

单例模式

确保一个类只有一个实例,并为其提供一个全局访问入口

观察者模式

应用

  • 游戏成就系统
    • 通过使用观察者模式,让成就系统与游戏中各个系统解耦
  • Unity里的消息中心
    • sendMessage和send

原型模式

使用特定原型实例来创建特定种类的对象,并且通过拷贝原型来创建新对象

游戏

序列型

双缓冲

存储两块缓冲区,通过交换缓冲区指针或引用,实现无缝过渡效果

应用

  • 应用情景
    • 我们需要维护一些被逐步改变着的状态量
    • 同个状态可能会在其被修改同时被访问
    • 希望避免访问状态的代码能看到具体工作过程
    • 希望能够读取状态但不希望等待写入操作的完成
  • 图形学
    • 图像渲染采用双缓冲模式,一个缓存用于展示当前帧,另一个正在被渲染代码写入数据,避免出现视觉错误
    • 处理动态模糊,当前帧和先前帧的一部分混合,从而使得产生的图像更接近真实摄像机拍摄效果
  • AI

游戏循环

游戏循环模式,实现游戏运行过程中对用户输入处理和时间处理的解耦。

使用场合

任何游戏或游戏引擎都拥有自己的游戏循环,因为游戏循环是游戏运行的主心骨。

更新方式

更新方法,通过每次处理一帧的行为来模拟一系列独立对象。

要点

  • 更新方法模式:在游戏中保持游戏对象的集合。每个对象实现一个更新方法,以处理对象在一帧内的行为。每一帧中,游戏循环对集合中的每一个对象进行更新。
  • 当离开每帧时,我们也许需要存储下状态,以备不时之需。

行为型

字节码

字节码模式,将行为编码为虚拟机器上的指令,来赋予其数据的灵活性。从而让数据易于修改,易于加载,并与其他可执行部分相隔离。有两种大风格:基于栈和基于寄存器

使用情况

  • 编译语言太底层,编写起来繁琐易错
  • 因编译时间太长或工具问题,导致迭代缓慢
  • 它的安全性太依赖编码者

子类沙盒

用一系列由基类提供的操作定义子类中的行为。

类型对象

创造一个类A来允许灵活的创造新的类,而类A的每个实例都代表了不同类型的对象。

解耦型

命令模式

原型

玩家脚本里一个具体按键对应执行一个具体事件函数

public void CheckInput()
{
	if(Input.GetButtonDown(KeyCode.X))
	{
		Jump();
	}
	......
}

改善

  • 可扩展性
    • 命令类Command,所有命令继承它
    • 输入处理类InputHandle,每个键位对应指针,可修改对应具体按键
    • 若支持无行为按键,可设指针为空,检查指针是否空来决定是否执行事件
  • 解耦
    • 将命令的发起者不再局限于玩家,执行者也不局限于玩家
      1. 将执行命令改为返回命令 命令延迟至调用具体化
      2. 传入命令发起者指针

应用

  • AI方面
    • 引入命令流,类似命令队列,不同类型AI可以混搭不同命令
  • 联机方面
    • 命令流序列化,从而传送数据流,实现命令回放
  • 撤销和重做
    • 引入命令栈,仅记录命令涉及变化的状态变量(持久化数据结构

组件模式

在单一实体跨越了多个领域时,为了保持领域之间相互解耦,可以将每部分代码放入各自的组件类中,将实体简化为组件的容器。

事件队列

在先入先出的队列中存储一系列通知或请求。发送通知时,将请求放入队列并返回。处理请求的系统在稍晚些的时候从队列中获取请求并进行处理。 这样就解耦了发送者和接收者,既静态又及时。

服务器定位器

服务类定义了一堆操作的抽象接口。具体的服务提供者实现这个接口。 分离的服务定位器提供了通过查询合适的提供者, 获取服务的方法,同时隐藏了提供者的具体细节和需要定位它的进程。

优化型

享元模式

通过将对象数据切分成两种类型,然后在每个对象实例之间共享内部状态数据来节省内存

  1. 内部状态
    • 又可称为上下文无关的状态/数据,即是不属于单一实例,能够被所有对象共享的数据
  2. 外部状态
    • 即是一个实例的状态,是唯一的,表现实例的差异性

应用

  • 场景渲染
    • 当你有太多对象并考虑对其进行轻量化时,如大型森林场景,树木内部状态数据是重复的,若通过共享可以很大程度减少数据存储量

对象池/缓冲池模式

使用固定的对象池重用对象,取代单独地分配和释放对象

数据局部性

  • 现代的CPU有缓存来加速内存读取,其可以更快地读取最近访问过的内存毗邻的内存。基于这一点,我们通过保证处理的数据排列在连续内存上,以提高内存局部性,从而提高性能。
  • 为了保证数据局部性,就要避免的缓存不命中。也许你需要牺牲一些宝贵的抽象。你越围绕数据局部性设计程序,就越放弃继承、接口和它们带来的好处。没有银弹,只有权衡。

脏标记模型

  • 脏标记,就是用来表示被标记的内容是否有被修改过的一个标志位。
  • 脏标识模式:考虑情况,当前有一组原始数据随着时间变化而改变。由这些原始数据计算出目标数据需要耗费一定的计算量。这个时候,可以用一个脏标识,来追踪目前的原始数据是否与之前的原始数据保持一致,而此脏标识会在被标记的原始数据改变时改变。那么,若这个标记没被改变,就可以使用之前缓存的目标数据,不用再重复计算。反之,若此标记已经改变,则需用新的原始数据计算目标数据。

空间分区

  • 对于一系列对象,每个对象都有空间上的位置。将它们存储在根据位置组织对象的空间数据结构中,让我们有效查询在某处或者附近的对象。 当对象的位置改变时,更新空间数据结构,这样它可以继续找到对象。
  • 最简单的空间分区:固定网格。想象某即时战略类游戏,一改在单独的数组中存储我们的游戏对象的常规思维,我们将它们存到网格的格子中。每个格子存储一组单位,它们的位置在格子的边界内部。当我们处理战斗时,一般只需考虑在同一格子或相邻格子中的单位,而不是将每个游戏中的单位与其他所有单位比较,这样就大大节约了计算量。

心得体会

在过去所开发的游戏中

观察者模式可以制作成就系统;

与事件队列配合可以制作事件中心;

频繁生成销毁的对象用对象池再好不过了;

命令模式可以使得玩家输入与逻辑处理解耦;

享元与双缓冲用于图形渲染。

使用了这些设计模式后,开发的逻辑变得更加清晰有条理;新的开发者加入也能迅速使用这些代码模块;而且优化了游戏渲染速度。

因此,合理巧妙的使用设计模式会让代码开发事半功倍。

posted @ 2021-03-16 19:47  AMzz  阅读(99)  评论(0编辑  收藏  举报
//字体