java - 如何避免从父类(super class)进行转换?

标签 java generics inheritance design-patterns casting

我正在开发一款具有不同游戏实体的游戏。它工作得很好,但我想摆脱我的代码中的一些转换操作。例如,当我检查子弹是否击中敌人时,我需要根据伤害( Bullet 的属性)。

我将实体及其相应的类存储在 map 中。它看起来像这样:

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

以下是我从 map 中放置、接收和删除实体的方法:

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;
            }
        }
    }

有什么方法可以避免子弹和敌人的这些类型转换操作吗?我正在考虑的一个解决方案是摆脱 map 并仅使用这些特定实体类型的许多列表,但这会破坏我的代码。

最佳答案

…Is there any way to avoid these casting operations for the Bullet and the Enemy?…

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;
            }
        }
    }       
}
...

这种方式涉及到两个接口(interface)的引入:DamagableIllable(当然,您可以将它们重命名为任何,以更准确地表达您的意图)。

就像每一个设计选择一样,也需要权衡。但这是摆脱这些强制转换的简单方法。

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



以下简化重构是从迭代过程中产生的,我喜欢将其称为“实验驱动开发”(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在我“开车”时所做的富有洞察力的观察(参见评论)他意识到,更简单的提议解决方案的副作用使得他的代码比他原来的问题所要求的甚至更简单

最初的问题是:“如何避免从父类(super class)进行转换?”。

This proposed solution如此灵活,OP 发现了一个机会,可以将其原始代码减少 2 整行加上 9 个不必要的字符(上面注释掉的内容)。

…But then i would have to implement those interfaces for ALL entities…

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

为任何其他实现自动生成的启动方法只需在现代 IDE 中单击鼠标即可。除非您是在操作系统中相当于 TextEdit 的版本中编写游戏?

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

关于java - 如何避免从父类(super class)进行转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63438801/

相关文章:

java - 在 IntelliJ IDEA 中启用代码样式

java - 在Android中为不同的功能使用不同的包是正确的编码约定吗?

java - 如何在Java中创建通用数组?

c# - 从泛型方法返回空值

javascript - 为什么要使用 Object.create?

java - 在 Java 中编写泛型函数时遇到问题

java - 使用 H2 插入外键时出现问题

Swift Generic Type Equatable 通过排序数组

java - 为什么内部类的子类需要封闭实例?

具有父构造函数参数的 Javascript 继承