2016-04-04 78 views
23

我知道從構造函數中調用可重寫的方法是一個壞主意。但我也看到它在Swing中隨處可見,其中代碼如add(new JLabel("Something"));一直在構造函數中出現。例如,以NetBeans IDE爲例。對構造函數中的可重複調用非常挑剔。然而,當它生成Swing代碼時,它會將所有這些方法調用放入initializeComponents()方法中,然後從構造函數中調用它!一種隱藏問題並禁用警告的好方法(NetBeans沒有「調用可覆蓋方法的私有方法從構造函數中調用」警告)。但不是真正的解決問題的方法。調用可重寫的方法,如Swing的add()在構造函數中

這是怎麼回事?我已經做了很多年,但總是對此感到不安。有沒有更好的初始化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 
     } 
    } 
} 

回答

6

爲了避免在構造函數中將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

NetBeans是生成函數私有。

private initializeComponents() {...} 

因此該方法是不可覆蓋的。只有受保護的公共方法是可以覆蓋的。

一個額外的函數可以讓您的代碼更加清潔Netbeans expample。 但一般而言,您可以節省使用私有方法來初始化類。

此外,如果您有多個構造函數,則使用一個額外的方法進行初始化是切實可行的。

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"; 
    } 
} 
+2

它可以保持代碼更清潔,但它不會使*更安全*。相反,問題現在隱藏了,但它仍然存在。即使init函數是私有的,它調用的函數,如add(),也不是。所以如果有人推翻add(),你會遇到完全相同的問題,只能隱藏在私有函數中!這是我的問題的重點。 –

+0

請參閱我的編輯例如。 –

1

OOP原則之一是:優先於繼承的組合。當我創建Swing GUI時,除非創建新的通用Swing組件(如JTreeTable,JGraph,JCalendar等),否則不會擴展Swing組件。

所以我的代碼如下所示:

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構建器;)

+0

這真的是唯一的缺點嗎?你如何將你的組件添加到容器?類似於'frame.add(myPanel.getComponent())'的代碼對我來說看起來很奇怪。 –

+1

對我來說這不是一個缺點。如果我在我的Eclipse的提案彈出窗口中看到超過100個方法,而這些方法是從JComponent派生的,那麼我會遇到更多的不便。 –

+0

看起來像是違反了德米特的原則。通過直接處理任何'getComponent()'返回值,你就是「與陌生人交談」。如果你使用組合,那麼組件應該被封裝。也就是說,如果它真的是構圖。你的例子看起來更像我的Builder模式,這是一個更好的主意。 –

0

再回到過了一段時間,閱讀接受的答案,我意識到有解決這個問題更簡單的方法。如果調用重寫方法的責任可以移動到另一個類,它也可以移動開了一個靜態方法,使用工廠方法模式:

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當子類化時,但至少不是每次創建實例時!正確記錄,這種方法既簡單又可靠。

相關問題