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

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

  1. Использование переопределения getPreferredSize() вместо использования setPreferredSize() для компонентов фиксированного размера
  2. Должен ли я избегать использования set(Preferred| Максимум|Минимум) Методы размера в 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, потому что setPreferredSize состояния Sets the preferred size of this component., но не как.


person nachokk    schedule 10.01.2014    source источник
comment
Я не считаю это глупостью. 1+ и жду ответа клеопатры.   -  person Hovercraft Full Of Eels    schedule 10.01.2014
comment
Это интересный вопрос. Проблемы возникают из-за того, что дизайнеры решили предоставить setPreferredSize вместо фактического принуждения людей к переопределению getPreferredSize. Если вы посмотрите только на компонент пользовательского интерфейса, кажется, что нет никакой логической причины для фактического использования setPreferredSize (это фактически не сломает компонент). Причиной переопределения getPreferredSize таким образом также может быть предотвращение изменения указанного вами значения другими людьми, что может быть сделано по определенной причине. Если вы хотите, вы можете вызвать super.getPreferredSize и проверить, является ли он нулевым или нет.   -  person MadProgrammer    schedule 10.01.2014
comment
Кстати, предыдущий разработчик нашего проекта использовал setPreferredSize практически для всего, к чему прикасался. Это полностью сломало приложение, когда мы перешли с Win XP на что-нибудь еще, поэтому setPreferredSize = боль в заднице ИМХО   -  person MadProgrammer    schedule 10.01.2014
comment
@HovercraftFullOfEels я где-то спрашивал ее, но не могу найти!   -  person nachokk    schedule 10.01.2014
comment
Я только что заметил, что вы возвращаете один и тот же экземпляр Dimension из своего метода getPreferredSize, это очень опасно, так как Dimension может изменяться вызывающей стороной, то есть они могут изменять значения. Это повлияет на значения, возвращаемые при последующих вызовах, то есть я не только смогу обойти setPreferredSize, но и нарушу ваш дизайн.   -  person MadProgrammer    schedule 11.01.2014
comment
@MadProgrammer вы не видите правку xD, которую я редактировал, прежде чем вы прокомментируете   -  person nachokk    schedule 11.01.2014
comment
@nachokk Я использовал кешированную страницу;)   -  person MadProgrammer    schedule 11.01.2014
comment
Не лучше ли было бы не переопределять setPreferredSizea и просто проверить super.getPreferrdSize, если он нулевой, вернуть свой размер, иначе вернуть супер значение? Это немного уменьшит сложность - IHMO   -  person MadProgrammer    schedule 11.01.2014
comment
@MadProgrammer да, я думаю, что это ответ, на который я надеялся (должен сказать, что я уже спрашивал об этом клеопатру, ответ был примерно таким, как вы сказали), но я не профессионал свинга, как вы, поэтому я хочу знать, почему мы переопределяем getPreferredSize, если он возвращается только при нулевом значении. Вы уверены, что можете сделать это полным ответом: D   -  person nachokk    schedule 11.01.2014
comment
но вы забыли об одном важном соображении: JComponents может возвращать любой реальный размер после вызова pack() или если он уже виден, именно так работают менеджеры макетов (за исключением NullLayout с использованием Insets), тогда все здесь может/может быть/о теории только, академическая дискуссия   -  person mKorbel    schedule 11.01.2014
comment
Это очень сложный вопрос. С одной стороны, как вы предположили, у нас есть ожидания от API, но, с другой стороны, бывают случаи, когда мы хотим контролировать размер, и много раз, когда вы не хотите, чтобы пользователь менял значение : P - Лично я считаю, что setPreferredSize следует игнорировать как можно больше (или относиться к нему как к защищенному). Одной из причин переопределения getPreferredSize является то, что люди не могут изменить размер, но в равной степени вы можете переопределить setPreferredSize и выдать неподдерживаемое исключение: P   -  person MadProgrammer    schedule 11.01.2014
comment
@mKorbel это правда, возможно, это просто академический вопрос, но, возможно, есть ли хорошая альтернатива тому, чтобы не нарушать lsp, возможно, делая наш inmutableSizeComponent и выбрасывая исключение, не поддерживающее   -  person nachokk    schedule 11.01.2014
comment
нет двусмысленности/проблемы: связанным свойством является константа pref (или min/max), настроенная в setXXSize, а не вычисляемая. Документация немного ... неполная, хотя, строго говоря, не существует, потому что полностью объяснена только как комментарий к коду. +1 за вопрос, конечно :-)   -  person kleopatra    schedule 11.01.2014
comment
@mKorbel JComponents может возвращать любой реальный размер после pack() как вы знаете, XXSizes являются просто подсказками для LayoutManager, который имеет полный контроль над тем, следует ли их соблюдать или нет :-) За подсказки как таковые отвечает сам компонент, который должен сделать все возможное, чтобы вычислить подсказки как можно точнее. Ничего академического, что я вижу..   -  person kleopatra    schedule 11.01.2014
comment
Окончательный размер @kleopatra можно установить тремя способами, все три рисуют прямоугольник с ожидаемым размером, я думаю, что с тем же потреблением ЦП / ГП (для которых предназначены LayoutManagers), затем результат / может быть, мы можем / говорим о хороших практиках, просто академический обсуждение того, как это могло бы быть без измеримого воздействия при работе на современном ПК с современной ОС Native и последней версией Java6/7   -  person mKorbel    schedule 11.01.2014
comment
@mKorbel конечный размер можно установить тремя способами - вернемся к нашим обычным отношениям: я понятия не имею, о чем вы говорите ;-) Существует ровно один способ установить размер компонент - либо сделанный LayoutManager, либо вручную - и это setBounds(...), поэтому вы должны иметь в виду что-то еще.   -  person kleopatra    schedule 11.01.2014
comment
@mKorbel война не предназначена - удален комментарий мусор   -  person kleopatra    schedule 11.01.2014


Ответы (2)


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

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

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

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

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

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

XXSize — это связанное свойство, не так ли?

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

person kleopatra    schedule 11.01.2014
comment
Я не очень хорошо понимаю ваше последнее предложение, мой английский не самый лучший, я должен прочитать его дважды все, setPrefferedSize существует с 1.5 в java.awt.Component, поэтому с этого момента это связанное свойство, поэтому, например, вы меняете свою метку, а prefferedSize становится изменено, но не изменено setPreferredSize, а как от 1.5, как это могло произойти, это не старый метод - person nachokk; 13.01.2014
comment
@nachokk Я хочу сказать, что это не связанное свойство, потому что оно не соответствует своему контракту, определенному спецификацией bean-компонентов, документ просто неверен. - person kleopatra; 14.01.2014
comment
Возможно, документы верны, а реализация неверна: P - person nachokk; 14.01.2014

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

person MadProgrammer    schedule 11.01.2014
comment
+1 да, очень хорошо описано, можно руководствоваться, но, пожалуйста, прочтите и мой последний комментарий к клеопатре - person mKorbel; 11.01.2014
comment
+1 за мощные предупреждения против setXXSize :-) Хотя я не согласен с вашей защитой сознательного нарушения контрактов методов - IMO, это так же зло, как использование ненадлежащим образом открытых API - person kleopatra; 11.01.2014
comment
@kleopatra Я, конечно, не согласен с вами, и я надеюсь, что всегда можно вырастить объект посредством наследования, которое продолжает соблюдать родительские требования, но я не думаю, что это всегда возможно, в самом строгом смысле. Иногда (все в редких случаях) необходимо изменить метод таким образом, чтобы изменить его. Хорошая идея? Это зависит от контекста, и в этом конкретном контексте это будет аргументом в пользу этого, поскольку я не вижу особого использования для установщика (или, по крайней мере, установщика, являющегося общедоступным). Нет, я не собираюсь по прихоти нарушать кучу договоров о методах - не сегодня;) - person MadProgrammer; 12.01.2014
comment
не знаю, какой ответ принять, оба одинаково хороши :) я просто бросаю монету - person nachokk; 11.02.2014