Я всегда вижу на этом сайте советы по переопределению 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()

  • Если для параметра предпочтительный размер было задано ненулевое значение, просто возвращается его . Если метод 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., но не как.

10
nachokk 10 Янв 2014 в 23:33

2 ответа

Лучший ответ

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

Нарушаем ли мы 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 предпочтительно: правда 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); 

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

5
kleopatra 11 Янв 2014 в 11:12

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

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

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

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

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

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

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

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

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

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

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

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

Так что куда ни глянь, ты кому-то ногу наступаешь ...

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

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

5
MadProgrammer 11 Янв 2014 в 05:20