如何解决如何避免从超类转换?
我正在开发具有不同游戏实体的游戏。它工作得很好,但是我想摆脱代码中的一些强制转换操作。例如,当我检查子弹是否击中敌人时,我需要投射两个对象,以能够基于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(...)
。
现在,我们需要证明在getListCasted(...)
中进行的未经检查的演员表的合理性。如果我们看一下方法add(...)
,我们会看到class-type(键)是列表中被容器的类型。因此,我们可以保证在某些Class<T extends Entity>
的密钥下存储List<T>
。因此,演员表是有道理的。
我们甚至可以使用Map
放弃Map<Class<?>,List<?>>
上的边界。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。