Должен ли каждый неизменный класс быть окончательным? - PullRequest
4 голосов
/ 09 апреля 2011

Я проектировал класс Card для использования в игре в Блэкджек.

Мой дизайн заключался в создании класса Card с помощью getValue (), который возвращает, например, 11 для J, 12 для Q и13 для K, а затем расширьте его классом BlackjackCard, чтобы переопределить этот метод, чтобы эти карты возвращали 10.

Тогда что-то ударило меня: объекты класса Card должны быть неизменными.Поэтому я перечитал Effective Java 2nd Edition, чтобы узнать, что делать, и там я обнаружил, что неизменяемые классы должны быть окончательными, чтобы избежать подкласса, чтобы нарушить неизменность.

Я также посмотрел в Интернете, и всем кажется,согласиться в этом вопросе.

Итак, должен ли класс Карты быть окончательным?

Как можно нарушить неизменность этого класса, расширяя его:

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 ]

5 голосов
/ 09 апреля 2011

Подкласс не может на самом деле изменить значения private final свойств своего родителя, но он может вести себя так, как если бы он имел, что и * Эффективная Java предостерегает против

Убедитесь, что класс не может быть расширен. Это предотвращает небрежное или злонамеренное подклассы от компрометации неизменное поведение класса ведет себя так, как будто состояние объекта имеет Измененное.

2 голосов
/ 09 апреля 2011

Ответ - да, карта должна быть окончательной.

Объединяя ответы К. Клазена и Люберка, смотрите следующее:

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 в полиморфном контексте, не могут зависеть от того, является ли он неизменным.

2 голосов
/ 09 апреля 2011

Вы можете сделать это:

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

}

Расширяющему классу даже не нужно объявлять тот же конструктор, что и родительский класс. Он может иметь конструктор по умолчанию и не заботится о последних членах родительского класса. [ Это утверждение неверно - см. Комментарии ].

1 голос
/ 30 марта 2012

Вы можете иметь 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, отметка *1013* методов final предотвратит их переопределение дочерним классом.

1 голос
/ 17 октября 2011

Если произвольный код может расширять неизменяемый класс, то произвольный код может создавать объекты, которые во многом похожи на неизменяемый класс, но не являются неизменяемыми.Если люди, которые пишут такой код, не смогут нанести вред никому, кроме себя, то такая ситуация может быть терпимой.Если кто-то может использовать такой класс для обхода мер безопасности, то это недопустимо.

Обратите внимание, что иногда полезно иметь расширяемый класс, который обещает неизменность от имени всех производныхклассы.Такой класс и его потребители, конечно, должны были бы полагаться на наследников класса, чтобы они не делали ничего странного и странного, но иногда такой подход все же может быть лучше, чем любая другая альтернатива.Например, может существовать класс, который должен выполнять какое-то действие в определенное время, при условии, что объекты класса могут быть произвольно псевдонимами.Такой класс не был бы ужасно полезным, если бы ни класс, ни какие-либо из его полей, ни какие-либо поля в каких-либо из его производных классов и т. Д. Не могли использовать производные типы, поскольку определение класса ограничивало бы, какие типы действий могут бытьвыполнила.Вероятно, наилучшим подходом было бы, чтобы класс не был объявлен как final, но в документации было бы очень ясно указано, что поведение изменяемых подклассов не будет согласованным или предсказуемым.

1 голос
/ 09 апреля 2011

В общем, это хорошая рекомендация.однако, если вы контролируете весь код, иногда полезно иметь возможность расширять неизменяемый класс (возможно, создать еще один неизменяемый класс с дополнительной информацией).как и в случае большинства рекомендаций, вы должны сделать разумный выбор относительно того, когда они имеют смысл, а когда нет.

1 голос
/ 09 апреля 2011

Когда они говорят, что неизменяемые классы должны быть окончательными, они имеют в виду, как вы можете обеспечить неизменность не потому, что что-то неизменяемое, оно должно быть окончательным.Это незначительное различие.Если вы не хотите, чтобы ваш класс продлевался, он должен быть окончательным.

...