子类/父类字段的最佳实践

4
我很难决定如何处理子类和父类的字段变量,以下是三种方法:
方法1:
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方法,必须进行复制并最终维护,这将是一个问题。
方法2:
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方法。我可能不希望其他类能够更改字段,即使在同一个包中也是如此。

方法3:

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

使用这种方法并涉及到很多字段时,构造函数的参数数量将会变得非常庞大,这种情况让人感觉不对劲。

看起来这应该是一个常见的问题,因此应该存在某种“最佳实践”。有没有最好的方法处理这个问题?


2
如果在Vehicle中声明了私有字段,那么Car中的私有字段就是多余的。您还可以调查“protected”访问修饰符。 - Fildor
3个回答

4

以下是几点想法:

  1. 如果可能的话,应该避免在基类和派生类之间共享字段,所以你不涉及“protected”字段的问题实际上是好事
  2. 同样地,最好避免使用setter方法,这就排除了你的第二个选项。

接下来...

你可以将第一个选项中的子类重写为:

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

在您的选项1中,那些数字实际上是常量,因为您的代码没有显示任何更改这些值的方法。因此,在派生类中使用字段是没有必要的!除非当然,您可能想象不同类型的汽车,并且需要允许3或5个轮子。在这种情况下,您将提供一个默认的ctor;以及一个需要该数量的轮子(然后将其存储在某个最终属性中)。
然后:当需要大量信息时,您是正确的,使用构造函数的option3会使您“爆炸”。但是:这只是设计问题的结果。因为:人们应该对字段数量保守。这意味着:如果您的类携带了太多字段,以至于通过构造函数初始化它们看起来像是一个问题,那么这表明您首先拥有太多字段!在这种情况下,您将查看您的模型,以确定哪些属性实际上属于您的类。
例如:在您的代码中,您将“成本”表示为基类的属性。但这是真的吗?它的“奖励”是否真的是任何车辆的基本属性?我的意思是:汽车只是一辆汽车;它并不关心它的价值。该值是某些外在属性,其他系统会将其强加给该车。这意味着:车辆不一定需要价格/成本属性。只有在车辆是某个处理其实体值的更大上下文中的实体时,才会开始考虑这一点。因此,其他EntityManager东西可能是跟踪车辆及其相应(当前)值的更好位置。

1
"常量作为您的代码并未显示出任何更改这些值的方式"... 嗯,一辆汽车通常被定义为四个轮子的车辆。虽然有三轮汽车...所以也许应该考虑将“4”设置为固定默认值,可以通过可选的CTOR-Param进行更改?但重点是特定类型的汽车在其使用寿命内不会改变其轮数,对吧? - Fildor
1
好的提醒,我随之改进了我的回答。你说得对,我假设轮子的数量是固定的;如果数量有变化,可能意味着相应的汽车已经到了寿命的尽头;-) - GhostCat

1

在这里,良好的实践是相对的;在你的情况下,它取决于你想要实现什么目标。

  1. Will any subclass of Vehicle have a number of wheels and a cost associated? If the answer is Yes, then it is good practice to add them to the superclass. If you may have TrackVehicle as subclass, then numberOfWheels is not applicable here and hence does not belong in the superclass.

  2. Ask yourself the question: do you really need setters? Will you have to change the state of your instance after creation? If not, don't add them: you can create a constructor in the superclass that takes the total number of required parameters and use it in every subclass:

    public Car(int numberOfWheels, int cost) {
        super(numberOfWheels, cost);
    }
    
  3. By trying to guess your intention, this would be my method of doing it:

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

    and a specific subclass where every Car has 4 wheels and a cost for the outside world that is actually much bigger than the initial one (just to show the fact that you can override a method if required, no need to duplicate it)

    public class Car extends Vehicle {
        public Car(int cost) {
            super(4, cost);
        }
        @Override
        public int getCost(){
            return cost * 2;
        }
    }
    
  4. About the 'constructor parameters will grow huge' problem: have a look at the 'Builder' design patters. (Effective Java - Builder pattern)


1
我在处理子类和超类的字段变量方面遇到了困难,不知道该选择哪种方式。
首先,您应该优先选择“组合优于继承”,这意味着具体类不会相互继承,只有接口会继承。
此外,您的问题某种程度上取决于您的类的目的。
类可以是“纯值类”,没有任何业务逻辑(也称为数据传输对象 - DTO),或者是“常规”对象。
DTO
当您设计DTO时,应将其创建为bean,这意味着应为每个属性创建公共getter方法。如果可能,您应该使DTOs不可变,这意味着所有成员变量都使用final关键字声明。然后,您必须通过构造函数设置值。
但是:一些框架要求具有默认构造函数和成员变量的setter的DTO。
常规对象
在所有其他类中,您不应直接或通过getter / setter提供对类的成员变量的访问。这将违反最重要的OO原则:信息隐藏,也称为封装。

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

eg.:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接