【问题标题】:Better to change parent's field or override getter?更改父字段或覆盖 getter 更好?
【发布时间】:2015-08-31 09:28:34
【问题描述】:

假设我有一个Projectile 类,它充当我游戏中所有射弹的基类。这包含最大速度、重力系数、反弹系数等的默认值。

public abstract class Projectile {

    protected float maxSpeed = 100.0f;
    protected float gravityCoefficient = 1.0f;
    protected float bounceCoefficient = 1.0f;
    ...

}

然后我有一堆子类,每个子类都可以选择覆盖其中的一些默认值。

这里有什么更好的方法?

1.在子构造函数中设置字段值

public class Arrow {

    public Arrow(){
        super();
        maxSpeed = 200.0f;
    }

}

2。让孩子覆盖 getter

public class Arrow {

    public float getMaxSpeed(){
        return 200.0f;
    }

}

我倾向于说第一种方法更好,因为这意味着可以直接访问该字段而无需任何额外的函数调用。但是,这确实意味着该值在对象创建期间设置了两次,一次由父级设置,一次由子级设置。

我在这里遗漏了什么吗?是否有,也许,另一种方法?

【问题讨论】:

  • 方法 1 是正确的方法
  • 第一种方法更好。您可以观察到没有任何缺点,但是第二种方法是一种非常糟糕的做法。除了使用返回固定值的 getter 之外,您还可以使用静态类常量。
  • 就我个人而言,我会将字段设为私有 - 如果可能,将设为 ​​final - 并引入一个允许子类指定值的 Projectile 构造函数。
  • 为什么不使用带有 speed 参数的构造函数?
  • @JonSkeet 这里有疑问,第一种方式违反了封装原则?

标签: java inheritance overriding


【解决方案1】:

直观地说,任何特定射弹的最大速度在其生命周期内都不太可能发生变化(即使在相同类型的不同实例可能具有不同最大速度的情况下),因此我倾向于最后的字段。我也倾向于将其定为最终版本 - 我很少使用非私有字段,除了真正的常量。

由于您对Projectile 有一些状态(字段),我会避免让getMaxSpeed 显示的最大速度与字段不同的混淆。

我可能会这样设计:

public abstract class Projectile {
    private final float maxSpeed;

    protected Projectile(float maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    // Only if you really need this...
    protected Projectile() {
        this(200f);
    }

    public final getMaxSpeed() {
        return maxSpeed;
    }
}

public class Arrow extends Projectile {
    public Arrow() {
        super(100f);
    }
}

重力系数和弹跳系数可以以类似的方式处理 - 或者如果所有这些确实充当“特定类型的每个实例的相同值”,您可以引入一个新类来表示这些常量,它将分隔来自常量限制/系数的类型实例的变化状态 - 每个实例可能只是对该​​新类的实例的最终引用。不幸的是,Java(以及至少一些类似的语言)并没有很好地模拟这种层次结构。这总是一个烦恼:(

【讨论】:

  • 我对将所有这些值作为参数提供给父构造函数的想法不太高兴,因为它们可能很多,而且大多数时候孩子应该继承来自父级的默认值。但是,我确实喜欢引入一个新类以将每个实例和每个类型的变量分开的想法。谢谢!
  • @Dan:好消息是,一旦你有了那个额外的类,你在构造函数中只需要一个参数。
  • 如果它确实是每个类型的常量,也许 maxspeed 应该是静态的?
  • @sharponb:不,因为那样你就找不到任何射弹的最大速度。使用 just 没有字段的属性将允许这样做,但在其他方面很烦人。这个问题是我最后一段的内容。它经常出现,而且很烦人。
【解决方案2】:

你应该有一个 setter 并使用它,这就是 setter 的用途。它将允许您将字段保密。另一个好处是,使用 Java Bean 约定将允许您使用诸如 Apache Commons BeanUtils 之类的库来填充和操作您的对象。您还可以将数据保存在数据库或文件中。

public abstract class Projectile {

    private float maxSpeed = 100.0f;  // default 

    protected void setMaxSpeed(float newSpeed) {
        maxSpeed = newSpeed;
    }
}

public class Arrow extends Projectile {

    public Arrow() {
        super();
        setMaxSpeed(200.0f);  // arrow specific values
    }
}

【讨论】:

  • 如果对象的最大速度在其生命周期内实际上可以变化,我只会添加一个设置器 - 至少 听起来 对我来说不太可能。感觉速度更有可能变化,但不是最大速度 - 在这种情况下,将它作为最终字段,在构造函数中设置会更有意义。
  • 这是一个很好的例子。但是假设您想从 xml 文件加载值(即允许修改游戏?)如果您遵循 java bean 约定并能够使用 3rd 方库,则容易得多。否则你将不得不有自定义加载器
  • 这听起来像是一个例外,而不是我的规则。除非我有充分的理由不这样做,否则我更喜欢将状态设为最终状态 - 在这种情况下,看起来最大速度取决于类型而不是类型的任何实例(至少对于 Arrow 而言),所以我为什么要保存XML 文件中的那个值?
  • 1) 值可能会随着游戏难度或可能的其他运行时间修改器而变化 2) 您可能希望在 beta 测试期间调整值
  • 所以在构造函数中提供值。在实例的生命周期中,它们都不需要更改。尽可能多地修复(理想情况下是所有东西,使用不可变类型)可以使代码很多更容易理解。显然,并非所有类型和所有状态都是如此,但看看这个 particular 示例,我认为 setter 不是一个好主意。
【解决方案3】:

第一种方法。在抽象基类中声明一个名为 modifyDefaults() 的方法。在每个类中实现它并调用构造函数,这样每当有人看到抽象类时,就可以断定您将修改子类的默认值。
或者,如果只有几个决定性参数,则将 Projectile 创建的责任交给 projectileFactory。

【讨论】:

    【解决方案4】:

    您对第一个答案的倾向应该是。它确实明确说明了以下内容:

    • 子类有责任创建自己的实例变量(属性)

    • 覆盖 getter 虽然在某些视图中听起来不错,但通常不会提供良好的可维护性。构造函数清楚地清楚地说明了额外的一组属性默认值。

    我不确定您的设计,但如果您的超类没有自己的状态,请尝试将其设置为 abstract 并且设计完全改变了我们在这种情况下讨论的内容(选项 2 可能到时候才算)。

    【讨论】:

      【解决方案5】:

      对于java来说,编译器优化和JIT优化对于性能的提升非常重要。
      第二段代码会更容易优化,不用担心额外的操作。

      【讨论】:

      • 在性能被证明至关重要之前,我更倾向于设计而不是性能。如果基类中的任何代码使用字段而不是方法,则会导致错误。在一个字段上设置状态,然后用返回不同常量值的方法“覆盖”它,这对我来说是一个非常糟糕的主意。
      • 是的,在大多数情况下这不是一个好的设计,但是,从我的角度来看,如果在子类中 maxSpeed 是一个常数,那么这种方式就可以了!
      • 那么为什么不设置字段,保持一致的观点,而不是矛盾的状态呢?
      • 如果子类不想要maxSpeed?它是封装并隐藏在 OOP 中。
      • 对不起,我根本不理解那个评论......但是由于超类已经有这个状态的字段,我认为在其他地方为其设置单独的值是一个非常糟糕的主意.
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-27
      • 2022-01-12
      相关资源
      最近更新 更多