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

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

我的设计是使用getValue()创建一个Card类,例如,返回11表示J,12表示Q,13表示K,然后使用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(); } } 

谢谢。

子类实际上不能修改其父级中的private final属性的值,但它可能表现得好像,这就是Effective Java警告的 :

确保无法扩展类。 这可以防止粗心或恶意子类通过表现为对象状态发生变化来破坏类的不可变行为。

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

结合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的类不能依赖于它是不可变的。

你可以这样做:

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

}

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

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

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

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

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

你可以有一个与游戏绑定的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将阻止子类覆盖它们。