| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Unity实现《游戏编程模式》
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。
优点
开闭原则。 你可以在不修改已有客户端代码的情况下在程序中创建新的命令。
你可以实现撤销和恢复功能。
你可以实现操作的延迟执行。
你可以将一组简单命令组合成一个复杂命令。
运行时切换连接至发送者的命令对象, 以此改变发送者的行为。
MoveCommand moveCommand = new MoveCommand(moveCommandReciever, direction, moveDistance, objectToMove);
JumpCommand jumpCommand = new JumpCommand(jumpCommandReciever, direction, jumpDistance, objectToMove);
Command command;
if (isMove){
command = moveCommand;
} else {
command = jumpCommand;
}
command.Execute();缺点
责任链模式***(GOF)*、 命令模式、 中介者模式** *(GOF)*和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
责任链的管理者可使用命令模式实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。
还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。
你可以同时使用命令和备忘录模式 *(GOF)*来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。
命令和策略模式看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。
原型模式可用于保存命令的历史记录。
你可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。
摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。
字符对象:A-Z每个字符作为一个对象,所有的A,有着相同的属性(内在属性),例如:width、height等;也有着不同的属性(外在属性),例如:pointSize
优点
缺点
你可以使用享元模式实现组合模式*(GOF)*树的共享叶节点以节省内存。
享元展示了如何生成大量的小型对象, 外观模式***(GOF)*则展示了如何用一个对象来代表整个子系统。
如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同。
允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。
仔细检查你的业务逻辑, 试着将其拆分为两个部分: 独立于其他代码的核心功能将作为发布者; 其他代码则将转化为一组订阅类。
声明订阅者接口。 该接口至少应声明一个 update方法。
声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。 记住发布者必须仅通过订阅者接口与它们进行交互。
确定存放实际订阅列表的位置并实现订阅方法。 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。
但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。
创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者。
在具体订阅者类中实现通知更新的方法。 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。
但还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下, 发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。
客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。
优点
缺点
责任链模式 * (GOF)、 命令模式、 中介者模式(GOF)*和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
中介者 *(GOF)*和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。
中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。
有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。
当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。
假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。
使用特定原型实例来创建特定种类的对象,并且通过拷贝原型来创建新的对象
创建原型接口, 并在其中声明 克隆方法。 如果你已有类层次结构, 则只需在其所有类中添加该方法即可。
原型类必须另行定义一个以该类对象为参数的构造函数。 构造函数必须复制参数对象中的所有成员变量值到新建实体中。 如果你需要修改子类, 则必须调用父类构造函数, 让父类复制其私有成员变量值。
如果编程语言不支持方法重载, 那么你可能需要定义一个特殊方法来复制对象数据。 在构造函数中进行此类处理比较方便, 因为它在调用 new运算符后会马上返回结果对象。
克隆方法通常只有一行代码: 使用 new运算符调用原型版本的构造函数。 注意, 每个类都必须显式重写克隆方法并使用自身类名调用 new运算符。 否则, 克隆方法可能会生成父类的对象。
你还可以创建一个中心化原型注册表, 用于存储常用原型。
你可以新建一个工厂类来实现注册表, 或者在原型基类中添加一个获取原型的静态方法。 该方法必须能够根据客户端代码设定的条件进行搜索。 搜索条件可以是简单的字符串, 或者是一组复杂的搜索参数。 找到合适的原型后, 注册表应对原型进行克隆, 并将复制生成的对象返回给客户端。
最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。
优点
缺点
确保一个类只有一个实例,并为其提供一个全局访问入口
优点
缺点
能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。
确定哪些类是上下文。 它可能是包含依赖于状态的代码的已有类; 如果特定于状态的代码分散在多个类中, 那么它可能是一个新的类。
声明状态接口。 虽然你可能会需要完全复制上下文中声明的所有方法, 但最好是仅把关注点放在那些可能包含特定于状态的行为的方法上。
为每个实际状态创建一个继承于状态接口的类。 然后检查上下文中的方法并将与特定状态相关的所有代码抽取到新建的类中。
在将代码移动到状态类的过程中, 你可能会发现它依赖于上下文中的一些私有成员。 你可以采用以下几种变通方式:
在上下文类中添加一个状态接口类型的引用成员变量, 以及一个用于修改该成员变量值的公有设置器。
再次检查上下文中的方法, 将空的条件语句替换为相应的状态对象方法。
为切换上下文状态, 你需要创建某个状态类实例并将其传递给上下文。 你可以在上下文、 各种状态或客户端中完成这项工作。 无论在何处完成这项工作, 该类都将依赖于其所实例化的具体类。
优点
缺点
用序列的操作模拟瞬间或者同时发生的事情
定义缓冲类封装了缓冲:一段可改变的状态。 这个缓冲被增量地修改,但我们想要外部的代码将修改视为单一的原子操作。 为了实现这点,类保存了两个缓冲的实例:下一缓冲和当前缓冲。
当信息从缓冲区中读取,它总是读取当前的缓冲区。 当信息需要写到缓存,它总是在下一缓冲区上操作。 当改变完成后,一个交换操作会立刻将当前缓冲区和下一缓冲区交换, 这样新缓冲区就是公共可见的了。旧的缓冲区成为下一个重用的缓冲区。
优点
缺点
独立的设计模式,需要它时自然会想起的模式
在游玩中不断运行。 每一次循环,它无阻塞地处理玩家输入,更新游戏状态,渲染游戏。 它追踪时间的消耗并控制游戏的速度
如果你使用游戏引擎,你不需要自己编写,但是它还在那里。
独立的设计模式,需要它时自然会想起的模式
通过每次处理一帧的行为模拟一系列独立对象;
游戏世界管理对象集合。
每个对象实现一个更新方法模拟对象在一帧内的行为。
每一帧,游戏循环更新集合中的每一个对象。
优点
缺点
将行为编码为虚拟机器上的指令,赋予其数据的灵活性
指令集 定义了可执行的底层操作。
一系列的指令被编码为字节序列。
虚拟机 使用 中间值栈 依次执行这些指令。
通过组合指令,可以定义复杂的高层行为。
优点
缺点
这一章节的近亲是GoF的解释器模式。两种方式都能让你用数据组合行为。
事实上,最终你两种模式都会使用。你用来构造字节码的工具会有内部的对象树。这也是解释器模式所能做的。
为了编译到字节码,你需要递归回溯整棵树,就像用解释器模式去解释它一样。 唯一的 不同在于,不是立即执行一段行为,而是生成整个字节码再执行。
用一系列由基类提供的操作定义子类中的行为。
基类定义抽象的沙箱方法和几个提供的操作。
将操作标为protected,表明它们只为子类所使用。
每个推导出的沙箱子类用提供的操作实现了沙箱函数。
优点
缺点
创造一个类A来允许灵活地创造新“类型”,类A的每个实例都代表了不同的对象类型。
定义类型对象类和有类型的对象类。
每个类型对象实例代表一种不同的逻辑类型。
每种有类型的对象保存对描述它类型的类型对象的引用。
实例相关的数据被存储在有类型对象的实例中,被同种类分享的数据或者行为存储在类型对象中。
引用同一类型对象的对象将会像同一类型一样运作。
这让我们在一组相同的对象间分享行为和数据,就像子类让我们做的那样,但没有固定的硬编码子类集合。
优点
缺点
这个模式处理的高层问题是在多个对象间分享数据和行为。 另一个用另一种方式解决了相同问题的模式是原型模式。
类型对象是享元模式的近亲。 两者都让你在实例间分享代码。使用享元,意图是节约内存,而分享的数据也许不代表任何概念上对象的“类型”。 使用类型对象模式,焦点在组织性和灵活性。
这个模式和状态模式有很多相似之处。 两者都委托对象的部分定义给另外一个对象。 通过类型对象,我们通常委托了对象是什么:不变的数据概括描述对象。 通过状态,我们委托了对象现在是什么:暂时描述对象当前状态的数据。
当我们讨论对象改变它的类型时,你可以认为类型对象起到了和状态相似的职责。
允许一个单一的实体跨越多个不同域而不会导致耦合。
单一实体跨越了多个领域。
为了保持领域之间相互分离,将每部分代码放入各自的组件类中。
实体被简化为组件的容器。
优点
缺点
这种模式与GoF的策略模式类似。 两种模式都是将对象的行为取出,划入单独的重述对象。 与对象模式不同的是,分离的策略模式通常是无状态的——它封装了算法,而没有数据。 它定义了对象如何行动,但没有定义对象是什么。
组件更加重要。它们经常保存了对象的状态,这有助于确定其真正的身份。 但是,这条界限很模糊。有一些组件也许根本没有任何状态。 在这种情况下,你可以在不同的容器对象中使用相同的组件实例。这样看来,它的行为确实更像一种策略。
事件队列在队列中按先入先出的顺序存储一系列通知或请求。 发送通知时,将请求放入队列并返回。 处理请求的系统之后稍晚从队列中获取请求并处理。 这解耦了发送者和接收者,既静态又及时。
优点
缺点
我在之前提到了几次,很大程度上, 这个模式是广为人知的观察者模式的异步实现。
就像其他很多模式一样,事件队列有很多别名。 其中一个是“消息队列”。这通常指代一个更高层次的实现。 事件队列在应用中,消息队列通常在应用间交流。
另一个术语是“发布/提交”,有时被缩写为“pubsub”。 就像“消息队列”一样,这通常指代更大的分布式系统,而不是现在关注的这个模式。
很像GoF的状态模式,需要一个输入流。如果想要异步响应,可以考虑用队列存储它们。
当你有一对状态机相互发送消息时,每个状态机都有一个小小的未处理队列(被称为一个信箱), 然后你需要重新发明actor model。
提供服务的全局接入点,避免使用者和实现服务的具体类耦合。
服务 类定义了一堆操作的抽象接口。
具体的服务提供者实现这个接口。
分离的服务定位器提供了通过查询获取服务的方法,同时隐藏了服务提供者的具体细节和定位它的过程。
优点
缺点
合理组织数据,充分使用CPU的缓存来加速内存读取
现代的CPU有缓存来加速内存读取。
它可以更快地读取最近访问过的内存的毗邻内存。
通过提高内存局部性来提高性能——保证数据以处理顺序排列在连续内存上。
优点
缺点
将工作延期至需要其结果时才去执行,避免不必要的工作
优点
缺点
独立的设计模式
放弃单独地分配和释放对象,从固定的池中重用对象,以提高性能和内存使用率
以空间换时间
这看上去很像是享元模式。 两者都控制了一系列可重用的对象。不同在于“重用”的含义。 享元对象分享实例间同时拥有的相同部分。享元模式在不同上下文中使用相同对象避免了重复内存使用。
对象池中的对象也被重用了,但是是在不同的时间点上被重用的。 “重用”在对象池中意味着对象在原先的对象用完之后分配内存。 对象池没有期待对象会在它的生命周期中分享什么。
将内存中同样类型的对象进行整合,能确保在遍历对象时CPU缓存总是满的。 数据局部性模式介绍了这一点。
优点
缺点
| Back | FazBrowse Home | New Git URL |