在构造函数中调用可重写的方法,如 Swing 的 add()

2022-09-01 21:25:10

我知道从构造函数调用可重写方法是一个坏主意。但我也看到,Swing 在任何地方都在这样做,在构造函数中一直会出现类似代码的代码。add(new JLabel("Something"));

以NetBeans IDE为例。它对构造函数中的可重写调用非常挑剔。然而,当它生成Swing代码时,它会将所有这些方法调用放入一个方法中......然后从构造函数调用它!隐藏问题并禁用警告的好方法(NetBeans 没有“从构造函数调用可重写方法的私有方法”警告)。但这并不是解决问题的真正方法。add()initializeComponents()

这是怎么回事?我已经这样做了很长时间,但总是对此感到不安。有没有更好的方法来初始化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
        }
    }
}

答案 1

为了避免在构造函数中将 Swing 组件连接在一起,可以简单地将连接的责任交给另一个对象。例如,您可以将布线职责分配给工厂:

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 组件,并让容器触发连接。下面是一个使用 Dagger 的示例:

@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

OOP 原则之一是:优先组合而不是继承。当我创建一个Swing GUI时,我从不扩展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构建器支持它;)