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


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

  1. использование переопределения getPreferredSize () вместо использования setPreferredSize() для компонентов фиксированного размера
  2. следует ли мне избегать использования методов set (Preferred / Maximum / Minimum) Size в Java Swing?
  3. переопределение setPreferredSize () и getPreferredSize()

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

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 делегата пользовательского интерфейса возвращает ненулевое значение значение, то верните его; в противном случае отложите до макета компонента менеджер.

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

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 cause setPreferredSize состояния Sets the preferred size of this component., но не как.
2 10

2 ответа:

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

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

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

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

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

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

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

В родословной J Component есть только косвенные доказательства: на самом деле компонент запускает свойство Changeevent в сеттере. Сам J component, кажется, документирует этот факт (выделено мной жирным шрифтом):

@beaninfo предпочтительно: правда граница: истина описание: предпочтительный размер компонента.

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

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 (по уважительным причинам). В своем бэкпорте к awt он мутировал в свойство бобов, которым никогда не был.

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

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

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

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

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

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

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

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

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

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

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

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

Итак, куда бы вы ни посмотрели, вы наступаете кому-то на пятки...

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

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