如何避免从超类转换?

如何解决如何避免从超类转换?

我正在开发具有不同游戏实体的游戏。它工作得很好,但是我想摆脱代码中的一些强制转换操作。例如,当我检查子弹是否击中敌人时,我需要投射两个对象,以能够基于health(子弹属性)减少damage(敌人的属性)。 / p>

我将实体及其对应的类存储在地图中。看起来像这样:

Map<Class<? extends Entity>,List<Entity>> entities;

以下是我从地图上放置,接收和删除实体的方法:

void add(Entity entity) {
    Class<? extends Entity> type = entity.getClass();
            
    if (!entities.containsKey(type)) {
        entities.put(type,new CopyOnWriteArrayList<>());
    }
    
    entities.get(type).add(entity);
}

List<Entity> getAll(Class<? extends Entity> type) {
    return entities.getOrDefault(type,Collections.emptyList());
}

void remove(Entity entity) {
    getAll(entity.getClass()).remove(entity);
}

最后,这是我的代码(在游戏循环中运行),用于检查子弹是否击中敌人:

for (Entity bullet : data.getAll(Bullet.class)) {
        for (Entity enemy : data.getAll(Enemy.class)) {
            if (bullet.box.overlaps(enemy.box)) {
                // Bullet hits Enemy
                Bullet bulletHit = (Bullet) bullet;
                Enemy enemyHit = (Enemy) enemy;
                
                enemyHit.health -= bulletHit.damage;
                if (enemyHit.health <= 0) {
                    data.remove(enemyHit);
                }
                data.remove(bulletHit);
                
                break;
            }
        }
    }

有什么方法可以避免对子弹和敌人进行这些施法操作?我正在考虑的一种解决方案是摆脱地图,只使用这些特定实体类型的许多列表,但这会使我的代码无效。

解决方法

…有什么方法可以避免对子弹和敌人进行这些施法操作?……

TL; DR 是的。有。 Consider this simple approach



详细答案

Here's one way来做到这一点(我认为最简单的方法)…

...
static void play(Gamer data){ 
     for ( Entity bullet : data.getAll(Bullet.class)) {
        for (Entity enemy : data.getAll(Enemy.class)) {
            if (bullet.getBox( ).overlaps( enemy.getBox( ) ) ) {
                // Bullet hits Enemy
                Damagable bulletHit = bullet;
                Illable enemyHit = enemy;
            
                int health = enemyHit.getHealth( );
                int damage = bulletHit.getDamage( );
                enemyHit.setHealth( health -= damage  );
                if ( health <= 0 ) {
                    data.remove(enemy);
                }
                data.remove(bullet);
            
                break;
            }
        }
    }       
}
...

这种方式涉及引入两个接口: Damagable Illable 当然,您可以将它们重命名为能更精确地表达您的意图的任何东西

就像每个设计选择一样,也需要权衡取舍。但这是摆脱那些演员的简单方法。

点击该链接页面顶部绿色的 开始 以运行实验



下面的简化重构是从我称之为“ 实验驱动开发”( EDD )的迭代过程中出现的。

...
/*Damagable bulletHit = bullet;
Illable enemyHit = enemy;*/
            
int health = enemy/*Hit*/.getHealth();
int damage = bullet/*Hit*/.getDamage();
enemy/*Hit*/.setHealth(health -= damage);
...

EDD 过程包括 RPP (“ 远程对编程”)。从 @DavidL 在我“ 开车”(查看评论)时所做的有见地的观察中,他意识到,较简单的建议解决方案产生了副作用他的代码 比他的原始问题要求的更简单

最初的问题是:“ 如何避免从超类强制转换? ”。

This proposed solution如此灵活,以至于OP认为有机会将他的原始代码减少2个整行加上9个不必要的字符(上面的注释掉的东西)。

...但是然后我将必须为所有实体实现这些接口...

在建议的解决方案中,您将必须实现 Entity 。无论如何,您目前都在这样做。无论您采用哪种设计,都必须实施它。

对于任何其他实现,

自动生成的启动程序方法都不会比现代IDE中的单击鼠标花费更多的精力。除非您用与OS等效的TextEdit编写游戏?

在当前代码中,您必须实现 Bullet 所需的任何行为以及 Enemy 所要求的任何行为。无论您选择哪种设计,都仍然必须实现相同的行为。

,

如果我们可以保证只在entities中存储非通用对象,则可以使访问器成为类型安全的(尽管我们不得不强制转换一次,但是我们将证明这种转换是合理的)。我们将要使用的概念与what ArrayList does,using an Object[] as internal data structure非常相似。

警告:实现按对象的类类型对对象进行分组。接口类型被完全忽略。

我们将把entities视为内部数据结构,即,我们不允许将对entites的任何引用泄漏到外部。使用此参数集,我们引入了一种内部方法,该方法从地图中获取List,并转换为正确的类型:

@SuppressWarnings("unchecked")
private <T extends Entity> List<T> getListCasted(Class<? extends T> type) {
    return (List<T>) entities.getOrDefault(type,Collections.emptyList());
}

请注意,此列表设置为私有列表,即仅供内部使用。

现在,我们重写getAll(...)remove(...)以使用此方法:

private <T extends Entity> List<T> getAll(Class<? extends T> type) {
    return new ArrayList<>(getListCasted(type));
}

public void remove(Entity entity) {
    getListCasted(entity.getClass()).remove(entity);
}

请注意,getAll(...)现在返回内部List的(可变)副本。

我们不需要修改方法add(...)

Ideone Demo

现在,我们需要证明在getListCasted(...)中进行的未经检查的演员表的合理性。如果我们看一下方法add(...),我们会看到class-type(键)是列表中被容器的类型。因此,我们可以保证在某些Class<T extends Entity>的密钥下存储List<T>。因此,演员表是有道理的。

我们甚至可以使用Map放弃Map<Class<?>,List<?>>上的边界。

Ideone Demo

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-