运行时动态选择方法;访客模式或反射的替代方案

2024-01-28

我正在开发一个小型游戏模板,其世界由如下节点组成:

World
|--Zone
|----Cell
|------Actor
|------Actor
|--------Item

Where a World可以包含多个Zone物体,一个Zone可以包含多个Cell对象等。

其中每一个都实现了Node接口,其中有一些方法,例如getParent, getChildren, update, reset等等。

我希望能够执行给定的Task在单个节点上或从节点递归地沿着树向下(由Task).

为了解决这个问题,我希望这是一个“可插入”系统,这意味着我希望玩家/开发人员能够动态向树中添加新类型。我还考虑过从基本类型进行转换:

public void doTask(Actor node)
{
    if(!(node instanceof Goblin)) { return; }
    Goblin goblin = (Goblin) node;
}

最初我被吸引使用访客模式 https://en.wikipedia.org/wiki/Visitor_pattern利用双重调度,允许每个例程(访问者)根据访问者的类型进行操作Node被访问。然而,这导致了一些并发症,特别是当我想添加一个新的Node键入树。

作为替代方案,我写了一个实用类 https://github.com/floralvikings/jenjin/blob/node-overhaul/jenjin-world-core/src/main/java/com/jenjinstudios/world/reflection/DynamicMethodSelector.java它使用反射来查找适用于的最具体的方法Node.

我现在关心的是性能 https://stackoverflow.com/questions/435553/java-reflection-performance;由于将会有相当大量的反射查找和调用,我担心我的游戏的性能(每秒可能有数百或数千个此类调用)会受到影响。

这似乎解决了两种模式的问题,但使每个新的代码Task uglier.

在我看来,我有三个选项来允许这种动态调度(除非我错过了一些明显/模糊的东西,这就是我在这里的原因):

  1. Visitor Pattern
    • Pros
      • 双重派遣
      • 表现
      • 任务中的整洁代码
    • Cons
      • 很难添加新的Node类型(不修改原始代码是不可能的)
      • 任务调用期间的丑陋代码
  2. Dynamic Invocation using Reflection
    • Pros
      • 可以添加新的Node放弃类型
      • 非常可定制的任务
      • 任务中的整洁代码
    • Cons
      • 表现不佳
      • 任务调用期间的丑陋代码
  3. Casting
    • Pros
      • 比反射性能更高
      • 可能比访客更有活力
      • 任务调用期间干净的代码
    • Cons
      • 代码气味
      • 性能低于 Visitor(没有双重调度,每次调用都进行强制转换)
      • 任务中的丑陋代码

我在这里错过了一些明显的事情吗?我熟悉“四人帮”的许多模式,以及《四人帮》中的模式。游戏编程模式 http://gameprogrammingpatterns.com/。任何帮助将不胜感激。

需要明确的是,我并不是在问其中哪一个是“最好的”。我正在寻找这些方法的替代方案。


因此,在对 Java 8 Lambda 以及如何反射式构建它们进行一些研究之后,我提出了创建一个BiConsumer来自Method我通过反射获得的对象,第一个参数是应调用该方法的实例,第二个参数是该方法的实际参数:

private static <T, U> BiConsumer<T, U> createConsumer(Method method) throws Throwable {
    BiConsumer<T, U> consumer = null;
    final MethodHandles.Lookup caller = MethodHandles.lookup();
    final MethodType biConsumerType = MethodType.methodType(BiConsumer.class);
    final MethodHandle handle = caller.unreflect(method);
    final MethodType type = handle.type();

    CallSite callSite = LambdaMetafactory.metafactory(
          caller,
          "accept",
          biConsumerType,
          type.changeParameterType(0, Object.class).changeParameterType(1, Object.class),
          handle,
          type
    );
    MethodHandle factory = callSite.getTarget();
    try {
        //noinspection unchecked // This is manually checked with exception handling.
        consumer = (BiConsumer<T,U>) factory.invoke();
    }catch (ClassCastException e) {
        LOGGER.log(Level.WARNING, "Unable to cast to BiConsumer<T,U>", e);
    }
    return consumer;
}

一旦这个BiConsumer已创建,它被缓存在HashMap使用参数类型和方法名称作为键。然后可以像这样调用它:

consumer.accept(nodeTask, node);

这种调用方法几乎完全消除了反射引起的调用开销,但它确实有一些问题/限制:

  • Because of the use of BiConsumer, only one parameter may be passed into the method (the first argument to the accept method must be the instance that should have the method invoked on it).
    • 这对于我的目的来说很好,无论如何我只想传递一个参数。
  • There is a non-trivial performance overhead when invoking a method with parameter types that haven't been seen before, as it must first be searched for reflectively.
    • 再说一遍,就我的目的而言,这是可以的;可接受的节点类型的数量不会很大,并且会在看到它们时快速缓存。在第一次“发现”参数类型组合的适当方法之后,开销非常小(我相信是恒定的,因为它是一个简单的HashMap抬头)。
  • 需要 Java 8(反正我已经在使用了)

我可以通过使用自定义功能接口(类似于Invoker类而不是Java的BiConsumer)但截至目前,它完全符合我想要的性能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

运行时动态选择方法;访客模式或反射的替代方案 的相关文章

  • 在 catch 块中重新抛出异常是否有意义?

    从 catch 块中抛出异常只是为了记录消息以便我们确定导致异常的原因是否有意义 Code public void saveLogs Logs logs throws RemoteException try LogsOps saveLogs
  • JDT - 尝试更改类型的超类。我不知道超级类的限定名称

    我有一个程序 除其他任务外 还必须使用 JDT 更改某些类的超类 我有两个字符串 其中包含要交换的超类的限定名称 例如 org example John 和 org example Smith 并且我正在解析整个 AST 搜索扩展这些类的类
  • JTree 避免重新加载后崩溃

    我正在尝试找到解决崩溃问题的方法JTree重新加载后 情况 JTree Office A Office A 1 Office A 1 1 Office A 1 2 Office B Office B 1 Office B 1 1 Offic
  • 克隆 dom.Document 对象

    我的目的是将xml文件读入Dom对象 编辑dom对象 其中涉及删除一些节点 完成此操作后 我希望将 Dom 恢复到其原始状态 而不实际解析 XML 文件 无论如何 我可以克隆第一次解析 xml 文件后获得的 dom 对象吗 这个想法是避免一
  • 如何将 Cucumber 中的数据表转换为对象列表?

    原标题 Java 中的 Cucumber DataTables 中的标量是什么意思 From 参考 Java 提供了几种标量类型 这些包括原始数字 类型 加上布尔值和字符 每个标量 原始 类型都有一个关联的包装类或 参考类型 阅读javad
  • 打印数组时出错

    我得到这个代码 import java util import java io public class Oblig3A public static void main String args OrdAnalyse O new OrdAna
  • java.time.LocalDate 到 java.util.Date

    转换的最佳方式是什么java time LocalDate to java util Date Date from dateToReturn atStartOfDay ZoneId systemDefault toInstant 我一直在尝
  • Logback 配置在单行上有异常吗?

    我的日志被提取 传输并合并到 elasticsearch 中 多行事件很难跟踪和诊断 有没有办法使用收集器和正则表达式将异常行分组到单个记录中登录配置 https logback qos ch manual layouts html xTh
  • 在Java中读取制表符分隔的文件

    我有以下代码来读取 Java 中的制表符分隔文件 while str in readLine null if str trim length 0 continue String values str split t System out p
  • 如何在 Java 中读取/转换 InputStream 为字符串?

    如果你有一个java io InputStream对象 您应该如何处理该对象并生成一个String 假设我有一个InputStream包含文本数据 我想将其转换为String 例如我可以将其写入日志文件 最简单的方法是什么InputStre
  • 使用 JavaFX 将可执行 Jar 限制为一个窗口

    我正在通过构建 JavaFX 应用程序E fx 剪辑 and Java场景生成器 基本功能是登录窗口 登录后 将打开新窗口 然后登录窗口消失 目前还处于原型阶段 用完eclipse后 我想要的功能都有了 启动时显示登录窗口 代码如下 Ove
  • 使用mapstruct映射不同类型列表的元素

    我们正在映射一个对象 该对象具有一个对象列表 这些对象都实现了父接口 但可能具有不同的实现 但当我们映射列表时 似乎只有来自 ParentClass 的值被映射 而不是来自子类的值 但直接映射子进程就可以了 public class Par
  • Maven编译错误:包不存在

    我正在尝试向现有企业项目添加 Maven 支持 这是一个多模块项目 前 2 个模块编译和打包没有问题 但我面临编译错误 我尝试在多个模块中使用相同的依赖项 我的结构是 gt parent gt pom xml gt module 1 gt
  • 使用 System.out.println 显示特殊字符

    我在将带有特殊字符的文本从网络服务发送或显示到数据库时遇到问题 在我的 Eclipse 上 我已将字符编码设置为 UTF 8 但它仍然不允许我显示字符 例如 像下面的代码一样简单的打印 String test System out prin
  • FocusEvent 没有获取 JFormattedTextField 的最后一个值,我如何获取它?

    我有两个JFormattedTextField我的物体JFrame目的 我想要通过这些值进行基本数学 加法 JFormattedTextField对象 我希望当焦点丢失第一个或第二个文本字段时发生这种情况 但当 focusLost 事件没有
  • 如何在 VSCode 中热重载 Tomcat 服务器

    我正在从 Eclipse IDE VSCode 分别用于编码 Java servlet 和 HTML CSS JS 网页 迁移到仅使用 Visual Studio Code 因为它的轻量级 我为 VSCode 安装了几个 Java 扩展 R
  • 如何为用户的活动设置计时器?

    如果用户在 5 小时内停止工作 我需要执行特定的方法 假设用户已登录 但他在 5 小时内没有向数据库的特定表添加任何记录 任何时候用户将记录添加到指定的表中 该特定用户的计时器都应该重置 否则它将继续运行 如果达到 5 小时 应用程序应显示
  • 旧的和奇异的 JVM 上 java.io.BufferedInputStream 的默认缓冲区大小是多少?

    我一直在为一篇关于以下内容的博客文章进行一些研究java io BufferedInputStream和缓冲区 显然 多年来 默认值已从区区 512 字节增长到 8192 字节 冒昧地 Sun 的 Java 7 实现 甚至在JDK 1 1
  • 获取类的公共属性而不创建它的实例?

    假设我们有一个 JavaScript 类 var Person function function Person name surname this name name this surname surname Person prototy
  • 最新版本 6.* Struts2 支持 Tomcat 10 吗? [复制]

    这个问题在这里已经有答案了 最新版本 6 Struts2 支持 Tomcat 10 吗 异常启动过滤器 struts2 java lang ClassCastException class org apache struts2 dispat

随机推荐

  • 手动向 Spring Security 提供引用 URL

    我们有一些购物车页面适用于访客和用户路径 我们希望允许用户在此过程中随时登录 但实际上并不想创建另一个登录页面 我希望我们可以简单地将用户重定向到现有登录并告诉 Spring Security 返回哪个 URL 我知道当会话超时和 或在没有
  • 为什么容器大小取决于绝对定位的子项?

    我正在尝试构建一个带有两个视频盒的类似 Skype 的界面 http jsfiddle net q9ER2 20 http jsfiddle net q9ER2 20
  • 创建一个可以在以后单击同一按钮时添加的可变数组?

    一般菜鸟问题 1 如何创建一个NSMutable数组在一个buttonClicked我可以在随后单击同一按钮时添加更多条目的操作吗 我似乎总是在每次点击时都从一个新数组开始 该数组只打印 1 个条目 这是一个按钮中最新的按钮标签 NSLog
  • 如何获取数组中元素的层次结构路径

    我总是想获得数组中元素的确切路径 数组示例 array a gt aaa b gt array bbb1 bbb2 gt array bbb3 bbb4 因此 为了到达 bbb4 我需要经过 b gt bbb2 gt bbb4 如何获取多维
  • 如何构造 LINQ to Entities 查询来直接加载子对象,而不是调用 Reference 属性或 Load()

    我是使用 LINQ to Entities 或实体框架 无论他们如何称呼它 的新手 我正在编写很多这样的代码 var item from InventoryItem item in db Inventory where item ID id
  • 如何使用 xsl-fo 和 apache fop 0.95 显示固定图像高度和宽度

    我正在尝试修复使用 fop 0 95 生成的 pdf 中图像的高度和宽度 这是用于它的代码
  • Nexus 6P 上硬件传感器的 Android 采样率变化

    我正在开发一个 Android 应用程序 用于研究 并且正在读取多个传感器数据 例如加速度计 陀螺仪 气压计等 所以我有 4 台 Nexus 6P 设备 全部配备最新版本工厂形象 https developers google com an
  • 到底是什么决定了 JavaScript 模块在 .html 中的执行顺序?

    我读到了首先加载最先出现的模块 https javascript info import export 这不是真的 在我回答这个问题之前 我了解到 首先执行没有导入的模块 叶模块 导入的模块只有在其导入的模块执行后才会执行 这让我能够解释这
  • 将 SQL STATISTICS TIME 和 IO 捕获到表中

    有没有办法捕捉STATISTICS IO and TIME在 T SQL 中 用于登录表 Sort of 与给出的统计数据相同SET STATISTICS TIME由查询统计 DMV 捕获 sys dm exec query stats h
  • 下一个更高/更低的 IEEE 双精度数

    我正在做高精度的科学计算 在寻找各种效果的最佳表示时 我不断想出想要获得下一个更高 或更低 可用双精度数的理由 本质上 我想要做的是将 1 添加到 double 的内部表示中的最低有效位 困难在于 IEEE 格式并不完全统一 如果要使用低级
  • 如何在反应传单中制作椭圆形?

    我想在反应传单中制作一个椭圆形 我已经检查过这个问题如何在react leaflet中制作一个椭圆形 https stackoverflow com questions 49089011 how can one make an ellips
  • Lombok 和 Jackson - 冲突/不明确的属性名称定义

    我收到一条警告消息 com fasterxml jackson databind JsonMappingException Conflicting ambiguous property name definitions implicit n
  • 使用随机森林的 AUC 基特征重要性

    我正在尝试使用随机森林和逻辑回归来预测二元变量 我的类别严重不平衡 Y 1 的大约 1 5 随机森林中的默认特征重要性技术基于分类准确性 错误率 这已被证明对于不平衡类来说是一个不好的衡量标准 请参阅here http www biomed
  • 为 mac ruby​​ 开发设置 .emacs 文件

    我在 Mac 上的 emacs 中加载 ruby 模式时遇到困难 emacs 文件位于 emacs 我已经向其中添加了几个命令 许多命令是从该站点粘贴的 但似乎没有一个被加载 有什么建议吗 我不确定文件是否未加载或者命令语法错误 我所需要的
  • 抛出“std::out_of_range”实例后调用终止

    我绝对是编程初学者 我在构建以下代码时遇到此错误 error terminate called after throwing an instance of std out of range what vector M range check
  • 发行说明,有什么用? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • windows下修改ip地址的脚本

    我使用计算机通过以太网与硬件进行通信 为了与此设备通信 我将 IP 设置为 192 168 0 11 将子网掩码设置为 255 255 255 0 将 IPv4 的默认网关设置为 192 168 0 1 为了使用互联网 我通过控制面板选择
  • 如何将几何数据字段显示为文本

    我将 DELPHI 与 ADO 和 SQL Server 2014 一起使用 在我们的数据库表中 有一个用于几何数据的空间列 我们可以在该字段中读取和写入数据 更多信息如下 https learn microsoft com de de s
  • 找不到 ios 文档的 /var/mobile/applications 目录

    我正在尝试在连接到 xcode 的 iPad 设备上运行发布版本以进行调试 我不确定 但猜测当我这样做时 文档目录最终会出现在我的本地计算机上的某个位置 而不是在 iPad 上 就像我在没有 xcode 的情况下运行它时一样 我得到如下目录
  • 运行时动态选择方法;访客模式或反射的替代方案

    我正在开发一个小型游戏模板 其世界由如下节点组成 World Zone Cell Actor Actor Item Where a World可以包含多个Zone物体 一个Zone可以包含多个Cell对象等 其中每一个都实现了Node接口