Переопределение getPreferredSize() прерывает LSP

Я всегда вижу советы на этом сайте переопределения getPreferredSize() вместо использования setPreferredSize(), как показано в этих предыдущих потоках, например.

См. этот пример:

public class MyPanel extends JPanel{

  private final Dimension dim = new Dimension(500,500); 

  @Override
  public Dimension getPreferredSize(){
      return new Dimension(dim);
  }

 public static void main(String args[]){
      JComponent component = new MyPanel();
      component.setPreferredSize(new Dimension(400,400));
      System.out.println(component.getPreferredSize());
 }

}

setPreferredSize()

  • Устанавливает предпочтительный размер этого компонента.

getPreferredSize()

  • Если для параметра preferredSize установлено значение, отличное от нуля, он возвращает его. Если метод getPreferredSize делегата UI возвращает непустую значение затем возвращает это; в противном случае отложить до компоновки компонентов менеджер.

Таким образом, это явно нарушает Принцип замены Лискова.

prefferedSize является связанным свойством, поэтому при его установке выполняется firePropertyChange. Итак, мой вопрос: когда вы переопределяете getPrefferedSize(), вам тоже не нужно переопределять setPreferredSize(..)?

Пример:

 public class MyPanel extends JPanel{

  private Dimension dim = null; 

  @Override
  public Dimension getPreferredSize(){
      if(dim == null)
       return super.getPreferredSize();
      return new Dimension(dim);
  }

  @Override
  public void setPrefferedSize(Dimension dimension){
        if(dim == null)
            dim = new Dimension(500,500);
        super.setPreferredSize(this.dim); //
  }

 public static void main(String args[]){
      JComponent component = new MyPanel();
      component.setPreferredSize(new Dimension(400,400));
      System.out.println(component.getPreferredSize());
 }

}

Теперь мы видим, что мы получаем одинаковые результаты, но слушатели получат уведомление с реальными значениями, и кроме того, мы не нарушаем LSP-причину setPreferredSize states Sets the preferred size of this component., но не так.

Ответ 1

Несколько аспектов этого интересного вопроса (Безумный уже упомянул о запасном другом-разработчике)

Мы нарушаем LSP в переопределении только getXXSize() (также и setXXSize())?

Нет, если мы сделаем это правильно:-) Первым авторитетом является API-документ свойства, наилучший из его происхождения, то есть Component:

Устанавливает предпочтительный размер этого компонента в постоянное значение. Последующие вызовы getPreferredSize всегда возвращают это значение.

Это контракт на связывание, поэтому, однако, мы используем getter, чтобы он учитывал постоянное значение, если оно установлено:

@Override
public Dimension getPreferredSize() {
    // comply to contract if set
    if(isPreferredSizeSet())
        return super.getPreferredSize();
    // do whatever we want
    return new Dimension(dim);
}

XXSize является связанным свойством - это?

В родословной JComponent имеются только косвенные доказательства: на самом деле, компонент запускает PropertyChangeEvent в сеттер. Сам JComponent документирует факт (смелый от меня):

@beaninfo   Предпочтительный: true    bound: true  описание: Предпочтительный размер компонента.

Что... неправильно неправильно: связанное свойство подразумевает, что слушатели должны уведомляться всякий раз, когда изменяется значение, то есть следующее (псевдо-тест):

JLabel label = new JLabel("small");
Dimension d = label.getPreferredSize();
PropertyChangeListener l = new PropertyChangeListener() ...
    boolean called;
    propertyChanged(...) 
        called = true;
label.addPropertyChangeListener("preferredSize", l);
label.setText("just some longer text");
if (!d.equals(label.getPreferredSize())
   assertTrue("listener must have been notified", l.called); 

... но терпит неудачу. По какой-то причине (не знаю, почему это могло сочтено целесообразным) они хотели, чтобы постоянная часть xxSize была связанным свойством - такие наложения просто невозможны. Возможно, было (дико угадать, конечно) историческую проблему: изначально сеттер был доступен только в Swing (по уважительным причинам). В своем backport to awt он мутировал в свойство bean, которого он никогда не было.

Ответ 2

Вообще говоря, нет простого (или правильного) ответа на этот вопрос.

Превышает ли getPreferredSize разрыв Лискова Замена принципа? Да (на основе имеющейся документации).

Но не больше ли расширение объекта? Какой смысл менять поведение метода, если он должен строго придерживаться ожиданий первоначальной реализации (да, есть хорошие примеры, когда вы должны это делать, например hashcode и equals и другие, где строка серая)?

В этом случае проблема, похоже, связана с неправильным использованием setXxxSize и тем фактом, что эти методы на самом деле public. Почему они публичные? Я понятия не имею, поскольку они являются причиной большего количества проблем, чем любая другая часть API (включая KeyListener).

Переопределение getPreferredSize предпочтительнее, поскольку изменение переносится с объектом, в отличие от вызова setPreferredSize извне владельца объекта/контекста

Поскольку getXxxSize предполагает предоставление подсказок с размерами в менеджере компоновки, на самом деле нет какой-либо разумной причины фактически иметь методы setXxxSize public, так как IMHO разработчикам не должно быть возиться с ними - требуется компонент для обеспечения наилучшей оценки требуемого размера на основе собственных внутренних требований.

Причиной переопределения getXxxSize таким образом было бы также запрещение другим людям изменять указанное вами значение, что может быть сделано по определенным причинам.

С одной стороны, как вы предположили, у нас есть ожидание API, но, с другой стороны, бывают случаи, когда мы хотим контролировать размер и много раз, когда вы не хотите, чтобы пользователь измените значение.

Мое личное чувство - как можно больше игнорировать setXxxSize (или рассматривать его как protected). Одна из причин переопределения getXxxSize - это остановить людей от изменения размера, но в равной степени вы можете переопределить setXxxSize и выбросить не поддерживаемое исключение.

Если бы вы задокументировали решения о игнорировании setXxxSize, это станет перерывом Принципа замещения Лискова? Возможно, поскольку компонент все еще может действовать как родительский.

Мое общее настроение - понять, что пытается сделать Принцип замещения Лискова, знать, когда вы должны его использовать, а когда нет. Не может быть четкого правила вырезания, которое соответствует каждому случаю, особенно если вы рассматриваете случай, когда сам дизайн неправильный.

В соответствии с вашим примером вы не должны переопределять getXxxSize или setXxxSize вообще, но вызывайте setXxxSize из конструктора, так как это будет поддерживать текущий контракт API, но также будет наступать на носки вызывая переопределяемые методы из конструктора...

Итак, везде, где вы смотрите, вы наступаете на кого-то пальцы...

Короче всего. Если это важно для вас (для поддержания принципа замещения Лискова), вы должны использовать setXxxSize из контекста ваших собственных компонентов. Проблема в том, что невозможно остановить кого-то от уничтожения ваших проектных решений с собственными ценностями и, как я уже сказал в комментариях, когда люди делают это, не понимая, что они делают, это просто заставляет всех elses работать кошмаром.

Не злоупотребляйте setPreferredSize, используйте его только из контекста экземпляра объекта и не вызывайте его извне... IMHO