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


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

    脏标记模型

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

    空间分区

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

    心得体会

    在过去所开发的游戏中

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

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

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

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

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

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

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

  • 相关阅读:
    HTTP && 缓存
    querySelector $() getElementBy区别
    Canvas 雾玻璃
    Github page
    Browserify
    关于布局和结构
    how to install flash
    kali update can’t found win7 loader
    arp spoofing
    How To Set Up Port Forwarding in Linux
  • 原文地址:https://www.cnblogs.com/AMzz/p/14545464.html
Copyright © 2020-2023  润新知