每个不可变的班级应该是最终的吗?

时间:2011-04-09 19:27:23

标签: java immutability final

我正在设计一个用于二十一点游戏的Card类。

我的设计是使用getValue()创建一个Card类,例如,返回J为11,Q为12,K为13,然后使用BlackjackCard类扩展它以覆盖该方法,以便这些卡返回10.

然后有些东西击中了我:Card类的对象应该是不可变的。所以我重新阅读Effective Java 2nd Edition,看看该做什么,我发现不可变类需要是最终的,以避免子类破坏不变性。

我也在互联网上看,每个人似乎都同意这一点。

Card类应该是最终的吗?

你如何打破这个阶级的不变性,扩展它:

class Card {
  private final Rank rank;
  private final Suit suit;
  public Card(Rank rank, Suit suit) {
    this.rank = rank;
    this.suit = suit;
  }
  public Rank getRank() {
    return rank;
  }
  public Suit getSuit() {
    return suit;
  }
  public int getValue() {
    return rank.getValue();
  }
}

感谢。

7 个答案:

答案 0 :(得分:5)

子类实际上不能在其父级中修改private final属性的值,但它可以表现,就像它具有的那样,Effective Java warns against

  

确保无法扩展课程。   这可以防止粗心或恶意   从属于破坏的子类   类的不可变行为   表现得像对象的状态一样   改变。

答案 1 :(得分:2)

你可以这样做:

class MyCard extends Card {

  public MyCard(Rank rank, Suit suit) {
    super(rank, suit);
  }

  @Override
  public Rank getRank() {
    // return whatever Rank you want
    return null;
  }

  @Override
  public Suit getSuit() {
    // return whatever Suit you want
    return null;
  }

  @Override
  public int getValue() {
    // return whatever value you want
    return 4711;
  }

}

扩展类甚至不必声明与父类相同的构造函数。它可以有一个默认的构造函数,并不关心父类的最终成员。 [该陈述错误 - 请参阅评论]。

答案 2 :(得分:2)

答案是肯定的,卡需要是最终的。

结合K. Claszen和lwburk的回应,请参阅以下内容:

public class MyCard extends Card {
    private Rank myRank;
    private Suit mySuit;

    public MyCard(Rank rank, Suit suit) {
        this.myRank = rank;
        this.mySuit = suit;
    }

    @Override public Rank getRank() { return myRank; }

    public void setRank(Rank rank) { this.myRank = rank; }

    @Override public Suit getSuit() { return mySuit; }

    public void setSuit(Suit suit) { this.mySuit = suit; }

    @Override public int getValue() { return myRank.getValue(); }
}

此扩展名完全忽略父状态,并将其替换为自己的可变状态。现在,在多态上下文中使用Card的类不能依赖于它是不可变的。

答案 3 :(得分:1)

当他们说不可变类应该是最终的时,他们指的是你如何确保不变性,而不是因为某些东西是不可变的,它必须是最终的。它是一个次要的区别。如果你不希望你的课程延长,那么它应该是最终的。

答案 4 :(得分:1)

一般来说,这是一个很好的建议。但是,如果您控制所有代码,那么有时能够扩展不可变类(可能创建另一个具有附加信息的不可变类)是有用的。与大多数建议一样,你必须做出明智的选择,以确定它们何时有意义,何时可能没有。

答案 5 :(得分:1)

如果任意代码可以扩展不可变类,那么任意代码都可以生成与不可变类很相似的对象,但不是不可变的。如果编写此类代码的人除了自己之外不会损坏任何东西,那么这种情况可能是可以容忍的。如果有人可以使用这样的类来绕过安全措施,那么就不应该容忍它。

请注意,有时可能会有一个可扩展的类,但它承诺代表所有派生类的不变性。当然,这样一个阶级及其消费者必须依靠班级的继承者不做任何奇怪和古怪的事情,但有时这种方法可能仍然比任何替代方法都要好。例如,一个人可能有一个应该在某个特定时间执行某些操作的类,条件是该类的对象可能被任意别名。如果类,任何字段,任何派生类中的任何字段等都不能使用派生类型,那么这样的类就不会非常有用,因为类的定义会限制什么类型的操作可能是执行。最好的方法可能是让类不被声明为final,但在文档中明确指出可变子类的行为不一致或不可预测。

答案 6 :(得分:1)

您可以拥有与游戏相关联的Valuator并从课程中移除getValue,这样Card可以是final

final class Card {
  private final Rank rank;
  private final Suit suit;
  public Card(Rank rank, Suit suit) {
    this.rank = rank;
    this.suit = suit;
  }
  public Rank getRank() {
    return rank;
  }
  public Suit getSuit() {
    return suit;
  }
}

估值师的工作方式如下:

interface CardValuator {
  int getValue(Card card);
}

class StandardValuator implements CardValuator {
  @Override
  public int getValue(Card card) {
    return card.getRank().getValue();
  }
}

class BlackjackValuator implements CardValuator {
  @Override
  public int getValue(Card card) {
    ...
  }
}

现在,如果您仍想保留Card层次结构,则标记Card方法 final将阻止子类覆盖它们。