【问题标题】:Different implementations for abstract class in JavaJava中抽象类的不同实现
【发布时间】:2015-01-03 03:14:22
【问题描述】:

我正在构建一个迷宫游戏,玩家必须在迷宫中导航,敌人在追他。

我想知道用“Zombie”和“Vampire”子类实现 Enemy 类的最佳方法是什么。目前这两个类别实际上是相同的,除了它们的颜色。运动策略是相同的,但是这可能会在未来发生变化。此外,我希望允许可扩展性,即如果客户想要添加更多具有不同移动策略的敌人,而不是在不更改现有代码库的情况下这样做。

我正在寻找不同的方法来做到这一点:

  1. 只需使用一个 Enemy 类,该类在其构造函数中接收一个字符串,并根据传入的是“Zombie”还是“Vampire”来决定制作“Enemy”的颜色。但是这不允许不同移动策略,如果添加许多其他敌人,就会有条件分支的建立。

  2. 具有抽象方法move()getColor() 的Abstract Enemy 类,这允许每个类定义自己的移动策略并持有对其颜色的引用。但是,如果敌人可能只是颜色不同,这不是有点矫枉过正。在我的实现中,敌人的移动都是一样的,但展示可扩展性的潜力总是一个加分项。

【问题讨论】:

  • 我肯定会选择第二个选项!使用一个抽象的 Enemy 类,实现了常用方法并声明为抽象的不常用方法。僵尸和吸血鬼扩展了 Enemy,因此它们必须实现其抽象方法。
  • 唯一的问题是,现在,我的 Zombie 和 Vampire move() 实现将是相同的。这是糟糕的设计实践吗?
  • 是的,最好只写一次相同的代码。如果您忘记只更新一种方法会发生什么?在 Enemy 类上实现这一点,如果您以后需要在另一个类中更改它,您可以覆盖它。
  • 如果是这种情况,那么您可以只在抽象类中定义 'defaukt' move() 方法,而不是在 Zombie 和 Vampire 子类中覆盖它。
  • @Dg123 如果你想稍微复杂一点,你总是可以建立一个运动“策略”,然后用它来构建你的敌人。这使您可以动态地改变运动(例如,如果敌人饿了或阳光来了)。

标签: java inheritance abstract-class subclass superclass


【解决方案1】:

我会为游戏的每个部分创建一个界面,例如移动、攻击或 npc(ness)。创建一个实现所有接口的不死敌人类,然后用吸血鬼和僵尸类对其进行扩展,仅覆盖颜色属性检索方法。我根本不会使用抽象类。

【讨论】:

  • 虽然同意使用接口很好,但你的亡灵类与选项 2 的 OPs 敌人类有何不同?所以基本上你也可以为不同的移动策略子类化不死类
  • 移动策略依赖于类。如果吸血鬼像不死生物一样移动,那么它应该简单地从不死生物继承。但是,如果不是,它将覆盖并因此具有自己的移动策略。 Punchline 这里没有使用抽象类,因为随着上面示例中项目的增长,我可以说最终所有方法都必须是抽象的。随着时间的推移,整个敌人的共同点太少了(他们会以不同的方式移动、不同的行动、不同的战斗等等),从而使抽象的使用变得过时。
【解决方案2】:

我会选择选项 1)

原因

据我了解 OP 问题,每个 Enemy 都有相同的方法和属性。只是他们的运动策略不同。所以我会生成一个抽象类MovementStrategy(顾名思义,你应该考虑策略模式)。只需将MovementStrategy 类型的成员添加到Enemy 类并将nextMove 方法委托给该对象即可。每个僵尸也可以有其他策略(简单/困难模式)。

您还可以考虑此类中的基本行为。 Enemy 类的层次结构不是一个好的选择 (IMO),因为这是它们唯一不同的地方。

但是

这仅适用于需求没有剧烈变化的情况!

编辑

正如评论中所暗示的:在这种情况下,您不能仅使用字符串来构造对象。

编辑 2

没有什么能阻止你使用这个变体来继承Enemy,但这并不是因为它们的策略不同。 我不喜欢这种方式,例如aleksamarkoni“将”实现选项 2,即他为超类型中的变量定义常量。因此,您从字面上删除了首先使用常量(类不是对象!)的变量这一点。 如果另一个僵尸有不同的绿色怎么办?你会用另一种阴影(LighterGreenZombie 类)生成第三类僵尸吗?那么基本上僵尸和吸血鬼之间有什么区别?除了颜色、名称和运动策略外,什么都没有!有了这些要求,将运动策略解耦使得完全子类化敌人毫无意义,并且更加干净!

【讨论】:

  • 那么如何将颜色和移动策略作为参数传递给构造函数而不是字符串?
  • 敌人已经在实施国家战略。 Hard/Medium/Easy 对 Enemy 类整体有不同的动作,不区分子类
  • @Dd123 也许我在这里没有正确理解您的问题。但是你为什么不这样做来解耦行为呢?也许 Subclass1 在 Easy 上的移动策略是 Subclass2 在 hard 上的策略。这样你就不会随时重复自己了
【解决方案3】:

第二个选项比第一个要好,尤其是当你要加入新类型的敌人时。如果你对这两种敌人有相同的动作,你可以把你的动作代码放在抽象的敌人类中,这样僵尸和吸血鬼就可以共享它。这是我的做法。

public abstract class Enemy {
    // every enemy has the color, so you can put it here
    protected String color;
    public void move() {
        // your basic move strategy here, this will include both the zombie and the vampire
    }
}

public class Zombie extends Enemy{

    // default constructor lets say zombies are black
    public Zombie() {
        color = "black";
    }

    // or you can have some color added throught constructor
    public Zombie(String color) {
        this.color = color;
    }

    // you dont have to override move function, because its the same as Enemy one
}

public class Vampire extends Enemy {

    // default constructor lets say vampires are red
    public Zombie() {
        color = "red";
    }

    // or you can have some color added throught constructor
    public Zombie(String color) {
        this.color = color;
    }

    // you dont have to override move function, because its the same as Enemy one
}

public class NewEnemy extends Enemy {

    // default constructor lets say new enemy is green
    public Zombie() {
        color = "green";
    }

    // or you can have some color added throught constructor
    public Zombie(String color) {
        this.color = color;
    }

    // lets say new enemy has diffrent move strategy
    @Override 
    public void move() {
        // new move strategy, only for the new enemy
    } 
}

【讨论】:

    【解决方案4】:

    到目前为止,第二个选项是更好的选择,因为将来如果您想制造更多具有相似功能的敌人,您可以再次扩展抽象类。

    此外,如果您想为每个敌人添加独特的元素,您可以更改相应的子类,而不是弄乱主要的 Enemy 类。

    最后,如果您想为每个敌人添加一个新的抽象特征/方法,那么在主抽象 Enemy 类中执行此操作将强制您在每个子类中实现它,以确保您进行全面的一致更改。

    【讨论】:

      猜你喜欢
      • 2017-02-27
      • 2012-01-20
      • 2020-10-20
      • 2011-12-01
      • 2014-08-18
      • 2021-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多