【问题标题】:Best practice for subclass/superclass fields [closed]子类/超类字段的最佳实践 [关闭]
【发布时间】:2017-04-19 19:47:17
【问题描述】:

我无法在这三种处理子类和超类的字段变量的方法之间做出选择。

方法一:

public abstract class Vehicle {
    public abstract int getNumberOfWheels();
    public abstract int getCost();
}

public class Car extends Vehicle {
    private int numberOfWheels;
    private int cost;

    public Car() {
        this.numberOfWheels = 4;
        this.cost = 10000;
    }

    public int getNumberOfWheels() {
        return numberOfWheels;
    }

    public int getCost() {
        return cost;
    }
}

使用这种方法,我必须在 Vehicle 的每个子类中实现相同的重复 getter 方法。我想这将是更复杂的 getter 方法的问题,必须复制并最终维护。

方法二:

public abstract class Vehicle {
    private int numberOfWheels;
    private int cost;

    public int getNumberOfWheels() {
        return numberOfWheels;
    }

    public int getCost() {
        return cost;
    }

    public void setNumberOfWheels(int numberOfWheels) {
        this.numberOfWheels = numberOfWheels;
    }

    public void setCost(int cost) {
        this.cost = cost;
    }
}

public class Car extends Vehicle {
    private int numberOfWheels;
    private int cost;

    public Car() {
        super.setNumberOfWheels(4);
        super.setCost(10000);
    }
}

使用这种方法,我必须实现我可能不想拥有的 setter 方法。我可能不希望其他类能够更改字段,即使在同一个包中也是如此。

方法三:

public abstract class Vehicle {
    private int numberOfWheels;
    private int cost;

    public class Vehicle(int numberOfWheels, int cost) {
        this.numberOfWheels = numberOfWheels;
        this.cost = cost;
    }

    public int getNumberOfWheels() {
        return numberOfWheels;
    }

    public int getCost() {
        return cost;
    }
}

public class Car extends Vehicle {
    private int numberOfWheels;
    private int cost;

    public Car() {
        super(4, 10000);
    }
}

用这种方法,加上很多字段,构造函数的参数量会变大,感觉不对。

这似乎是一个足够普遍的问题,以至于存在某种“最佳实践”。有没有最好的方法来做到这一点?

【问题讨论】:

  • Car 中的私有字段如果在 Vehicle 中声明,则它们是多余的。您可能还想调查“protected”访问修饰符。

标签: java inheritance field subclass superclass


【解决方案1】:

我无法在这三种处理子类和超类的字段变量的方法之间做出选择。

首先,你应该更喜欢组合而不是继承,这意味着具体的类不会相互继承,只有接口。

除此之外,您的问题在某种程度上取决于您上课的目的。

类可以是没有任何业务逻辑的“纯值类”(又名数据传输对象 - DTO)或“常规”对象。

DTO

当您设计 DTO 时,您应该将它们创建为 beans,这意味着您应该为每个属性创建公共 getter 方法。无论如何,您应该使您的 DTO 不可变 这意味着所有成员变量都使用 final 关键字声明。然后你必须通过构造函数设置值。

但是:某些框架需要具有默认构造函数和成员变量设置器的 DTO。

常规对象

在所有其他类中,您不应直接或通过 getter/setter 提供对类成员变量的访问。这将违反最重要的 OO 原则:信息隐藏 aka 封装

初始值应通过构造函数设置,当您需要修改成员值时,应提供具有业务相关名称的方法。

例如:

class Vehicle {
  private int speedInMph;
  private final int maximumSpeedInMph;
  public Vehicle(int initialSpeedInMph, int maximumSpeedInMph){
    this.speedInMph=initialSpeedInMph;
    this.maximumSpeedInMph=maximumSpeedInMph;
  }
  public void accelerateBy(int accelerationInMph){
    this.speedInMph+=accelerationInMph;
    if(maximumSpeedInMph<this.speedInMph)
       this.speedInMph=maximumSpeedInMph;
  }

  public void decelerateBy(int decelerationInMph){
    this.speedInMph-=decelerationInMph;
    if(0>this.speedInMph)
       this.speedInMph=0;
  }
}

【讨论】:

    【解决方案2】:

    好的做法在这里是相对的;就您而言,这取决于您要达到的目标。

    1. Vehicle 的任何子类是否有多个轮子和相关的成本?如果答案是肯定的,那么最好将它们添加到超类中。如果您可能将TrackVehicle 作为子类,那么numberOfWheels 在这里不适用,因此不属于超类。

    2. 问自己一个问题:你真的需要二传手吗?创建后是否必须更改实例的状态?如果没有,请不要添加它们:您可以在超类中创建一个构造函数,该构造函数获取所需参数的总数并在每个子类中使用它:

      public Car(int numberOfWheels, int cost) {
          super(numberOfWheels, cost);
      }
      
    3. 通过尝试猜测您的意图,这将是我这样做的方法:

      public abstract class Vehicle {
          private int numberOfWheels;
          private int cost;
      
          public Vehicle(int numberOfWheels, int cost) {
              this.numberOfWheels = numberOfWheels;
              this.cost = cost;
          }
          public int getNumberOfWheels() {
              return numberOfWheels;
          }
          public int getCost(){
              return cost;
          }
      }
      

      和一个特定的子类,其中每个Car 有 4 个轮子,并且外部世界的成本实际上比最初的要大得多(只是为了表明您可以在需要时覆盖方法,无需复制它)

      public class Car extends Vehicle {
          public Car(int cost) {
              super(4, cost);
          }
          @Override
          public int getCost(){
              return cost * 2;
          }
      }
      
    4. 关于“构造函数参数将变得巨大”问题:查看“生成器”设计模式。 (Effective Java - Builder pattern)

    【讨论】:

      【解决方案3】:

      这里有几个想法:

      1. 实际上很好你没有进入 protected 领域的故事;如果可能,应避免在基类/扩展类之间共享字段。
      2. 同样,避免使用 setter 也是的做法。这有点排除了你的第二个选择。

      继续……

      您可以将选项 1 中的子类重写为:

      @Override // always use that when OVERRIDING methods!
      public int getNumberOfWheels() { return 4; }
      

      在您的选项 1 中,这些数字实际上是常量,因为您的代码没有显示任何更改这些值的方法。所以派生类中不需要使用字段!当然,除非您可以想象不同类型的汽车,并且还需要允许 3 或 5 个轮子。在这种情况下,您将提供一个默认 ctor;以及一个采用该数量的轮子(然后将其存储在某些 final 属性中)。

      那么:你是对的,当需要大量信息时,使用构造函数的选项 3 会“炸毁”你。但是:无论如何,这只是设计问题的结果。因为:无论如何,对于字段的数量应该保守。含义:如果您的类包含如此多的字段,以至于通过构造函数初始化它们看起来像是一个问题,那么这表明您首先获得了太多字段!在这种情况下,您将查看您的 模型 以确定哪些属性真正属于您的类。

      示例:在您的代码中,您将“成本”表示为基类的属性。但这是真的吗?它的“奖品”真的是任何车辆必不可少的财产吗?我的意思是:汽车只是汽车;它不“关心”它的价值。该值是一些外在属性,其他系统将强加于该汽车。含义:车辆不一定需要价格/成本属性。只有当车辆是处理其实体值的某些更大上下文中的实体时,您才会开始考虑这一点。因此,其他一些 EntityManager 可能是跟踪车辆及其相应(当前)值的更好地方。

      【讨论】:

      • “常量,因为您的代码没有显示任何更改这些值的方法” ...好吧,汽车主要被定义为具有 4 个轮子的车辆。虽然有 3 辆汽车......所以也许应该考虑一个(恒定的)默认值“4”,可以通过可选的 CTOR-Param 进行更改?但关键是,特定(类型)汽车在其生命周期内不会改变其轮数,对吧?
      • 神点;我相应地增强了我的答案。你是对的;我假设轮子的数量是固定的;该数字的变化可能表明相应汽车的使用寿命结束;-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-03
      • 2016-05-21
      • 1970-01-01
      • 1970-01-01
      • 2010-10-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多