当前位置: 首页 > news >正文

设计模式-3D引擎中的设计模式

概述

设计原则

  • 开放封闭原则 对扩展开放,对修改关闭。

  • 依赖倒置原则 高层组件和底层组件(的实现),都依赖于抽象层,以降低耦合。

  • 组合复用原则 组合和继承都能达到设计目的,优先考虑使用组合。

  • 迪米特法则/最少知识原则 一个对象对其它对象的了解应尽可能少,从而降低对象之间的耦合,提高系统可维护性。

  • 单一职责原则 一个类应该只有一个引起变化的原因,即一个类的职责应该单一,只做一类事情,或对外提供一种功能。

  • 里氏替换原则 子类可以替换父类。任何时候,派生类可以替换基类,系统功能不会受到影响。

设计原则(版本2)

  • 找出应用中可能需要变化之处,把它们独立出来,和不变的代码分开。变化是扩展/实现,不变是框架。

  • 针对接口编程,而不是针对实现编程。

  • 多用组合,少用继承

  • 努力降低耦合

  • 对扩展开放,对修改关闭

  • 依赖抽象,不要依赖事项

  • 最少知识原则

  • 别调用(打电话给)我们,我们会调用(打电话给)你

  • 一个类应该只有一个引起变化的原因

耦合的类型

  • 内容耦合 一个模块直接修改或操作另一个模块的数据,或一个模块不通过正常入口而转入另一个模块。最高耦合,避免出现。

  • 公共耦合 两个或以上模块共同引用一个全局数据项(数据结构,共享通信区,共享内存区)。难以确定哪个模块操作了数据。

  • 外部耦合 一组模块都访问同一全局简单变量(而不是同一全局数据结构)。

  • 控制耦合 一个模块,通过传送开关、标志、名字等控制信息(控制信号),明显控制选择另一个模块的功能。

  • 标记耦合 两个模块之间传递的是数据结构,如数组,记录名,文件名,数据结构地址,等。

  • 数据耦合 模块之间通过参数传递数据。

设计模式

创建型模式

关注对象创建,将对象创建和使用分离。包括:简单工厂,工厂方法,抽象工厂,原型,建造者,单例。

简单工厂 Simple Factory

定义工厂类,提供接口,根据不同参数类型创建并返回不同类型的对象,这些类型的对象通常具有相同基类。调用者无需关心创建细节。

简单工厂可以不简单

引擎当中,有很多简单工厂,工厂内部通过 map<key,Constructor>的方式记录了类型-创建方法/对象 的映射。比如资产管线里的 Importor,启动时,为每种资产后缀名注册导入器类型,当需要时,根据后缀名和对应的类型名,通过反射创建导入器。

工厂方法 Factory Method

定义一个用于创建对象的接口,由子类实现,使类的实例化延迟到子类。

抽象工厂 Abstract Factory

提供一组接口,让这些接口创建一系列相关,或者相互依赖的对象,即按照产品族生产产品。

引擎当中,对平台差异的封装,通常采用这种模式。比如文件系统,渲染设备,抽象一组逻辑相关的接口类,同一个抽象工厂创建的这些接口类的实现,可以协同工作,实现复杂的功能。

原型 Prototype

通过对指定对象的赋值/克隆来创建对象。该类对象的创建过程可能很复杂,或者创建过程未知,只能通过克隆创建。

引擎当中,GameObject是通过组织 Component 来实现特定 GameObject 功能,可以有任意多种组合类型,因此我们实现了 Instantiate 接口,通过已有对象,创建新对象,就是原型模式。

ECS 架构也是原型模式。

建造者 Builder

将复杂对象的创建,与它的表示分离,使得同样的构建过程,可以创建不同的表示。

建造者提供建造过程的接口,以及获取建造结果(对象)的接口,其建造过程由 Director 驱动。

引擎当中,对于图形API的封装,也会用到该模式:经过封装的图形设备的启动,有其固定的流程,但是不同设备类型,对于流程中的每个步骤,实现各不相同。所以,RHI 既用到了抽象工厂,也用到了建造者。建造者跟抽象工厂相比,除了创建对象,还负责组织这些对象之间的关系。

单件 Singleton

保证一个类仅有一个实例存在,并且提供全局接口访问该实例。

在实现上,根据实例创建时机,分为两种:

  • 用到时才创建,也叫懒汉模式

    class Singleton{
    public:static Singleton* Instance(){static Singleton _inst;return &_inst;}
    };
  • 程序初始化时创建,不管后面会不会用到,也叫饿汉模式。

    //.h
    class Singleton{
    public:static Singleton* Instance(){return _Instance;}
    private:Singleton(){_Instance = this;}static Singelton* _Instance = nullptr;
    };
    //.cpp
    Singleton theSingleton; // 创建方式1
    int main(){Singleton theSingleton;  // 创建方式2
    }

Github 上的单件实现 https://github.com/AlexWorx/ALib-Singletons。实现了基于模板的单例,其文档,同时描述了在PC dll 下,懒汉单例模式存在的问题,并给出了解决方案。

结构型模式

关注对象之间的关系,如何组合协作以获得灵活的结构,简化设计。包括:装饰,外观,组合,享元,代理,适配器,桥接。

装饰 Decorator

别名:包装器 Wrapper

动态地给一个对象添加一些额外的职责。

装饰器和被装饰对象具有相同的基类,这样装饰器就可以取代被装饰对象,且可以用一个或多个装饰器包装一个对象。

外观 Facade

提供统一的接口,来访问子系统中的一群接口,简化系统的使用,以及客户端和系统间的解耦。

整理系统功能,将客户端需要的系统接口封装到更少的类中。

ERA1.5 引擎中,对引擎封装的接口层,就是 Facade 模式。

组合 Composite

将对象组合成树形结构,以表示“部分-整体”的层次接口,使对单个对象和组合对象的操作/访问具有一致性。

引擎中的 GameObject-Component 就应用了这种模式,但是有针对性的做了一些优化

在此基础上,引擎进行了一些优化,以对组件做一些限制,因为作为叶子节点,很多功能是不需要的,同时游戏对象和组件行为差异化较大:

进一步思考,可以将差异,实现在一个组件里,像 unity 中的 gameobject 不负责层级树和父子关系维护,而是由 transform 来完成。

享元 Flyweight

运用共享技术,有效的支持大量细粒度的对象。

  • 程序中某种对象,数量很大,或对象本身很大

  • 这种对象可以被分为内部状态和外部状态

  • 内部状态,所有对象都是一样的

  • 所有对象维持外部状态,共享内部状态

引擎渲染时,要使用大量的网格,或贴图,而这些网格和贴图可以被多个模型共享,是典型的享元模式的应用:

代理 Proxy

为其它对象提供一种代理,以控制对这个对象的访问。主要目的是为客户端增加额外功能,约束,或隐藏复杂细节。

引擎中的对象引用 ObjectReference 就是该模式:

引擎主要功能,是利用资产组织场景,以进行计算/渲染。场景创建过程中,是对“磁盘”上资产文件的引用,比如硬盘某个目录下的文件/文件夹。但是资产文件不仅在本地磁盘,还可能由其它文件协议提供:ftp://, file://, http://, memory:// 等。如果不希望对每种资产文件形式实现场景组织逻辑,就需要定义抽象的代理层:

适配器 Adapter / 包装器 Wrapper

将一个类的接口转换成客户希望的另外一个接口。该模式使得原本由于接口不兼容而不能一起工作的类,可以一起共组。

适配器可以通过派生或组合的方式,实现被适配对象的引用。

stl 中的 stack, queue 就是对 dequeue 进行适配,表现出 stack , queue 的行为。

引擎中的物理系统,可以应用该模式:

桥接 Bridge

将抽象部分与实现部分分离,使它们可以独立的变化和扩展。

  • 抽象部分一般指业务功能

  • 实现部分一般指具体平台的实现

  • 抽象部分,实现部分,还可以归结为系统功能的两个维度,在两个维度上,利用组合,而不是派生,这样每个维度可以分别自由变化/扩展。

更一般的,

  • Abstraction:抽象部分接口,同时包含指向 Implementor 类型对象的指针。

  • RefinedAbstraction:扩展抽象部分接口,主要是实现 Abstraction 定义的接口,并调用 Implementor 的接口。

  • Implementor:实现部分接口,定义实现类的接口。可能跟 Abstraction 一致,也可能不一致。

  • ConcreteImplementor:实现 Implementor 的接口

引擎中,渲染系统可以应用该模式:

  • Abstraction 侧,是渲染器抽象层及实现逻辑

  • Implementor 侧,是设备层抽象层及实现层逻辑

行为型模式

关注对象行为和交互,定义一组对象如何协作完成整体任务。包括:模板方法,策略,观察者,命令,迭代器,状态,中介者,备忘录,职责链,访问者,解释器。

模板方法 Template Method

定义一个功能的算法骨架,将骨架的一些步骤延迟到子类实现,使得子类可以不改变一个算法的结构,即可重定义一些步骤。

引擎中,该模式应用比较多,比如组件的多数接口,都应用了:

void Component::Update()
{if (m_updateCount == 0){Start();  // OnStart()m_updateCount++;}OnUpdate();
}

引擎中,应用该模式实现了最基本的执行过程:(虽然不是一模一样,但是实现模式时根据具体情况作出以下变动也很正常)

策略 Strategy / Policy

定义一系列算法/功能,并将它们封装起来,使其可以根据上下文互换。

  • Strategy 策略,定义算法/功能接口

  • ConcreteStrategy 实现特定的策略

  • Context 上下文,用于确定使用一个策略

策略模式也是引擎中使用比较多的模式:可以用数据,或特定逻辑来定义上下文,以确定应用哪个算法,例如资产导入管线:

观察者 Observer

定义对象之间1对多的依赖关系

引擎中,为了降低耦合,大量使用该模式进行事件传递。核心是委托,但是其实现比观察者模式要复杂一些:

命令 Command

将一个请求封装成对象,从而可以将请求对象以参数形式进行传递;对请求排队,或记录,以实现重做或撤销操作。

引擎编辑器的用户操作,应用命令模式,封装成对象,以实现重做或撤销:

迭代器 Iterator / 游标 Cursor

提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露内部表示。

stl 基本模式之一

引擎中的 GameComponentQuery 实践了该模式,提供了迭代器,来遍历收集到的组件

状态 State

允许一个对象,在其内部状态改变时,改变其行为,对象看起来似乎修改了它的类。

需要考虑状态转换:

  • 由状态完成

  • 由转换表完成

状态模式最常用法是实现状态机

参与者:

  • Context:上下文,比如FSM

    • 定义用户感兴趣的接口

    • 维护当前状态

    • 可能还负责创建和维护所有状态

  • IState:定义状态接口,以完成 Context 功能

  • ConcreteState:具体的状态类

引擎中的动画状态机,就应用了状态模式,参考设计(实际上考虑很多其它方面,要比这复杂的多):

中介者 Mediator

用一个中介对象来封装一系列的对象交互,使各个对象不需要显式的互相引用,从而降低耦合,而且可以独立改变它们之间的交互。

该模式适合解决,一组对象,需要通过复杂的互相调用/通讯实现逻辑,导致产生复杂的依赖/引用关系。

我们最熟悉的应用中介者模式的模式,就是 MVC 模式。编辑器的大量逻辑,都实践了该模式

  • Mediator 中介者,定义接口,与各个同事通信。

  • ConcreteMediator 具体中介者,通过接口协调同事对象完成交互,实现逻辑。

  • Colleague 同事,参与系统,具体实现一些功能。同事之间通过中介者与其它同事通信。

备忘录 Memento

在不破环封装的前提下,捕获对象的内部状态,并在该对象以外保存这个状态,以后可以将该对象恢复到保存的状态。

引擎中的对象,很多需要保存(在内存/磁盘中),只需要将这些对象的关键数据成员(即内部状态)序列化到流,即可

职责链 Chain of Responsibility

使多个对象都有处理请求的机会,避免请求发送者和接收者之间的耦合关系。将这些对象串成一条链,沿着该链传递请求,直到有一个对象处理它。

提交请求的对象,并不明确哪个对象会处理它(甚至没有对象处理,或多个对象处理(某些系统的特殊需求))。

引擎中的输入系统,快捷键系统,都可以应用该模式。

访问者 Visitor

表示 作用于系统中对象的操作,使得可以在不改变这些对象的前提下,定义/扩展/改变作用于这些对象的操作。简单说,允许一个/一组操作,应用到一个/一组对象上,将对象和其上的操作解耦。

参与者:

  • Visitor 访问者,为系统中每种类型声明 visit 操作。

  • ConcreteVisitor 具体访问者,根据具体算法,实现访问系统中类型的接口。同时提供上下文,存储中间结果。

  • Element 元素,定义 accept 操作,以 visitor 为参数。

  • ConcreteElement 具体类型

  • ObjectStructure 对象结构,能够枚举元素,并提供接口访问元素。

引擎中的特效系统,粒子数据,和对其的各种操作,是需要分离的,可以参考该模式进行设计(根据具体情况,不会照搬模式)

解释器 Interpreter

定义一个语言的文法(语法规则),并建立一个解释器,解释执行该语言的句子。

参与者:

  • AbstractExpression 抽象表达式,声明一个抽象的解释操作。

  • TerminalExpression 终结符表达式,实现文法中终结符相关的解释操作。

  • NonterminalExpression 非终结符表达式,实现非终结符的解释操作。非终结符表达式可以包含终结符,非终结符表达式,因此该解释过程通常是递归的。

  • Context 上下文,包含解释器中的其它全局信息。

引擎中,FlowGraph 的执行,就是实践了该模式。由于并没有实现很底层的语法规则,仅仅是定义了节点,以及执行流程,所以设计上不是按照 terminal / nonterminal 来设计的,而是设计了过多样化的 Expression 来实现复杂功能。

http://www.dtcms.com/a/406940.html

相关文章:

  • Linux安装配置Redis 7.2.3教程
  • 山西省城乡住房建设厅网站网站建设需要多少钱小江
  • 网站建设背景需要写些什么考研哪个培训机构比较好
  • JavaEE 初阶第二十五期:IP协议,网络世界的 “身份通行证”(一)
  • 有一个做炫舞官网活动的网站企业邮箱注册申请126
  • 服务器跨域问题CORS的解决
  • MyBatis进行级联查询
  • MySQL8.0.26-Linux版安装
  • 济南网站建设_美叶网络网址域名查询
  • 深入了解linux网络—— UDP网络通信
  • 招商加盟的网站应该怎么做宝坻做网站哪家好
  • 视频网站开发工具网站备案中是什么意思
  • 物理媒介和分组交换原理
  • Linux常用命令53——file
  • 西双版纳 网站建设网络建设与运维初级
  • 【Python】文件处理(一)
  • win10怎么做网站wordpress wooyun
  • 织梦网站登录网上做网站赚钱吗
  • Linux数据安全与备份策略完全指南
  • 哈尔滨网站建设服务公司暴雪游戏服务中心
  • wordpress 关闭评论网站优化排名提升
  • 硅基计划5.0 MySQL 壹 初识MySQL 初版
  • Linux之挂载新的硬盘(超详细!)
  • 部署 GitLab 服务器
  • C++项目:仿muduo库高并发服务器-------connection模块
  • 网站建设需要的资质互联网保险的发展现状
  • 8-机器学习与大模型开发数学教程-第0章 预备知识-0-8 编程与数值计算基础(浮点数精度、溢出、数值稳定性)
  • php网站开发书微信公众号手机网站开发
  • 做网站需要工商执照吗代人做网站
  • Go基础:模块化管理为什么能够提升研发效能?