Подтвердить что ты не робот

Вызов передовых методов, таких как Swing add() в конструкторе

Я знаю, что вызов переопределяемых методов из конструкторов - плохая идея. Но я также вижу, что это происходит везде с Swing, где код типа add(new JLabel("Something")); встречается в конструкторах все время.

Возьмите NetBeans IDE, например. Это очень разборчиво по поводу сложных вызовов в конструкторах. И все же, когда он генерирует код Swing, он помещает все эти вызовы метода add() в метод initializeComponents()... который затем вызывается из конструктора! Хороший способ скрыть проблему и отключить предупреждение (NetBeans не имеет "частного метода, который вызывает переопределяемые методы, вызывается из предупреждения конструктора" ). Но на самом деле это не способ решить проблему.

Что здесь происходит? Я делал это целую вечность, но всегда испытывал неловкое чувство об этом. Есть ли лучший способ инициализации контейнеров Swing, за исключением создания дополнительного метода init() (и не забудьте вызвать его каждый раз, что является скучным)?

Пример

Вот очень надуманный пример того, как все может пойти не так:

public class MyBasePanel extends JPanel {
    public MyBasePanel() {
        initializeComponents();
    }

    private void initializeComponents() {
        // layout setup omitted
        // overridable call
        add(new JLabel("My label"), BorderLayout.CENTER);
    }
}

public class MyDerivedPanel extends MyBasePanel {
    private final List<JLabel> addedLabels = new ArrayList<>();

    @Override
    public void add(Component comp, Object constraints) {
        super.add(comp);
        if (comp instanceof JLabel) {
            JLabel label = (JLabel) comp;
            addedLabels.add(label); // NPE here
        }
    }
}
4b9b3361

Ответ 1

Чтобы избежать соединения компонентов Swing вместе с конструктором, вы можете просто передать ответственность за проводку другому объекту. Например, вы могли бы предоставить проводные функции Factory:

public class MyPanelFactory {
    public MyBasePanel myBasePanel() {
        MyBasePanel myBasePanel = new MyBasePanel();
        initMyBasePanel(myBasePanel);
        return myBasePanel;
    }

    public MyDerivedPanel myDerivedPanel() {
        MyDerivedPanel myDerivedPanel = new MyDerivedPanel();
        initMyBasePanel(myDerivedPanel);
        return myDerivedPanel;
    }

    private void initMyBasePanel(MyBasePanel myBasePanel) {
        myBasePanel.add(new JLabel("My label"), BorderLayout.CENTER);
    }
}

Или вы можете изо всех сил и создавать все компоненты Swing с контейнером для инъекций зависимостей, а контейнер запускает проводку. Вот пример с кинжалом:

@Module
public class MyPanelModule {
    static class MyBasePanel extends JPanel {
        private final JLabel myLabel;

        MyBasePanel(JLabel myLabel) {
            this.myLabel = myLabel;
        }

        void initComponents() {
            this.add(myLabel, BorderLayout.CENTER);
        }
    }

    static class MyDerivedPanel extends MyBasePanel {
        private final List<JLabel> addedLabels = new ArrayList<>();

        MyDerivedPanel(JLabel myLabel) {
            super(myLabel);
        }

        @Override
        public void add(Component comp, Object constraints) {
            super.add(comp);
            if (comp instanceof JLabel) {
                JLabel label = (JLabel) comp;
                addedLabels.add(label);
            }
        }
    }

    @Provides MyBasePanel myBasePanel(@Named("myLabel") JLabel myLabel) {
        MyBasePanel myBasePanel = new MyBasePanel(myLabel);
        myBasePanel.initComponents();
        return myBasePanel;
    }

    @Provides MyDerivedPanel myDerivedPanel(@Named("myLabel") JLabel myLabel) {
        MyDerivedPanel myDerivedPanel = new MyDerivedPanel(myLabel);
        myDerivedPanel.initComponents();
        return myDerivedPanel;
    }

    @Provides @Named("myLabel") JLabel myLabel() {
        return new JLabel("My label");
    }
}

Ответ 2

Один из принципов ООП: Предпочитает состав над наследованием. Когда я создаю графический интерфейс Swing, я никогда не расширяю компоненты Swing, кроме того, что создаю новый компонент Swing общего назначения (например, JTreeTable, JGraph, JCalendar и т.д.).

Итак, мой код выглядит так:

public class MyPanel {
     private JPanel mainPanel;
     public MyPanel() {
         init();
     }
     private void init() {
          mainPanel = new JPanel();
     }
     public Component getComponent() {
         return mainPanel;
     }
}

public class MyComposedPanel {
     private JPanel mainPanel;
     public MyComposedPanel() {
         init();
     }
     private void init() {
          mainPanel = new JPanel();
          mainPanel.add(new MyPanel().getComponent());
     }
     public Component getComponent() {
         return mainPanel;
     }
}

У этого способа есть один недостаток: нет GUI-конструктора, который его поддерживает;)

Ответ 3

Возвращаясь через некоторое время и прочитав принятый ответ, я понял, что есть еще более простой способ решения этой проблемы. Если ответственность за вызов переопределяемых методов может быть перенесена в другой класс, ее также можно перенести на статический метод, используя шаблон метода factory:

class MyBasePanel extends JPanel {

    public static MyBasePanel create() {
        MyBasePanel panel = new MyBasePanel();
        panel.initializeComponents();
        return panel;
    }

    protected MyBasePanel() {
    }

    protected void initializeComponents() {
        // layout setup omitted
        // overridable call
        add(new JLabel("My label"), BorderLayout.CENTER);
    }
}

class MyDerivedPanel extends MyBasePanel {

    private final List<JLabel> addedLabels = new ArrayList<>();

    public static MyDerivedPanel create() {
        MyDerivedPanel panel = new MyDerivedPanel();
        panel.initializeComponents();
        return panel;
    }

    protected MyDerivedPanel() {
    }

    @Override
    public void add(Component comp, Object constraints) {
        super.add(comp);
        if (comp instanceof JLabel) {
            JLabel label = (JLabel) comp;
            addedLabels.add(label); // no more NPE here
        }
    }
}

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

Ответ 4

Netbeans генерирует функцию private.

private initializeComponents() {...}

Таким образом, метод не является допустимым. Только защищенные и общедоступные методы являются допустимыми.

Дополнительная функция сохраняет код намного чище для расширения Netbeans. Но в целом вы можете использовать частные методы для инициализации классов.

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

class Foo {

   int x,y;
   String bar;

   public Foo(x) {
      this.x = x;
      init();
   }

   public Foo(y) {
      this.y = y;
      init();
   }
   private void init() {
      // .. something complicated or much to do
      bar = "bla";
   }
}