每个不可变的类都应该是最终的吗?

发布于 2024-10-31 18:35:50 字数 665 浏览 1 评论 0原文

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

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

然后我突然想到:Card 类的对象应该是不可变的。所以我重新阅读了Effective Java 2nd Edition,看看该怎么做,我发现不可变类需要是final的,以避免子类破坏不可变性。

我也在网上查了一下,大家似乎都同意这一点。

那么 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();
  }
}

谢谢。

I was designing a Card class to be used in a Blackjack game.

My design was to make a Card class with a getValue() that returns, for example, 11 for J, 12 for Q and 13 for K, and then extend it with a BlackjackCard class to override that method so that those cards return 10.

Then something hit me: objects of the Card class should be immutable. So I re-read Effective Java 2nd Edition to see what to do and I there I found that immutable classes need to be final, to avoid a subclass to break the immutability.

I also looked in Internet and everyone seems to agree in that point.

So should the Card class be final?

How can you break the immutability of this class, be extending it:

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

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

筑梦 2024-11-07 18:35:50

子类实际上无法修改其父类中的 private final 属性的值,但它可以表现,就好像它已经修改一样,这就是 Effective Java 警告

确保课程无法延长。
这可以防止粗心或恶意
子类免受损害
类的不可变行为
表现得好像对象的状态已经
改变了。

A subclass cannot actually modify the values of private final properties in its parent, but it could behave as though it has, which is what Effective Java warns against:

Ensure that the class can't be extended.
This prevents careless or malicious
subclasses from compromising the
immutable behavior of the class by
behaving as if the object's state has
changed.

诠释孤独 2024-11-07 18:35:50

答案是肯定的,卡必须是最终的。

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

Answer is yes, Card needs to be final.

Combining the responses of K. Claszen and lwburk, see the following:

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

This extension completely ignores the parent state and replaces it with its own, mutable state. Now classes that use Card in polymorphic contexts can't depend upon its being immutable.

所有深爱都是秘密 2024-11-07 18:35:50

您可以这样做:

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

}

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

You can do this:

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

}

The extending class does even not have to declare the same constructor as the parent class. It can have a default constructor and does not care anything about the final members of the parent class. [That statement is wrong - see the comments].

我的奇迹 2024-11-07 18:35:50

当他们说不可变类应该是最终的时,他们指的是如何确保不变性,而不是因为某些东西是不可变的,所以它必须是最终的。这是一个很小的区别。如果您不希望延长课程时间,则应以最终决定为准。

When they say immutable classes should be final they are referring to how you can ensure immutability not that because something is immutable it has to be final. Its a minor distinction. If you don't want your class ever extended it should be final.

乜一 2024-11-07 18:35:50

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

In general, this is a good recommendation. however, if you control all the code, then sometimes it is useful to be able to extend an immutable class (possibly to create another immutable class with additional info). as with most recommendations, you have to make intelligent choices as to when they make sense and when they may not.

锦欢 2024-11-07 18:35:50

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

请注意,有时拥有一个可扩展的类会很有用,但它代表所有派生类保证不变性。当然,这样的类及其使用者必须依赖该类的继承者不要做任何奇怪的事情,但有时这种方法可能仍然比任何替代方法更好。例如,一个人可能有一个类,该类应该在某个特定时间执行某些操作,但该类的对象可以任意别名。如果该类、其任何字段、其任何派生类中的任何字段等都不能使用派生类型,则这样的类不会非常有用,因为类的定义将限制可以执行的操作类型执行。最好的方法可能是不将该类声明为最终类,但在文档中明确指出可变子类的行为将不一致或不可预测。

If arbitrary code can extend an immutable class, then arbitrary code can produce objects that behave a lot like the immutable class, but aren't immutable. If people who write such code would be unable to damage anything but themselves, then such a situation may be tolerable. If someone could use such a class to bypass security measures, then it should not be tolerated.

Note that it can at times be useful to have a class which is extensible, but which promises immutability on behalf of all derived classes. Such a class and its consumers would, of course, have to rely upon inheritors of the class not to do anything weird and wacky, but sometimes such an approach may still be better than any alternative. For example, one might have a class which is supposed to perform some action at some particular time, with the proviso that objects of the class may be arbitrarily aliased. Such a class would not be terribly useful if neither the class, nor any of its fields, nor any fields in any of its derived classes, etc. could use derived types, since the definition of a class would restrict what types of actions could be performed. The best approach would probably be to have the class not be declared final, but make very clear in the documentation that the behavior of mutable subclasses would not be consistent or predictable.

只是偏爱你 2024-11-07 18:35:50

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

You could have a Valuator that's tied to the game and remove the getValue from the class, this way Card can be 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;
  }
}

And valuators work like this:

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) {
    ...
  }
}

Now, if you still want to keep your Card hierarchy, marking the Card's methods final will prevent a child class from overriding them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文