2012-09-14 49 views
3

有人可以幫助新手程序員瞭解他的解決方案是否正確?正確的方法來防止子類實例化,而無需調用所需的初始化方法?

我的問題是類似以下兩種:

What's wrong with overridable method calls in constructors?

Factory pattern in C#: How to ensure an object instance can only be created by a factory class?

問題:我想有子類,這將區別僅在他們的初始化方法。但是,我也想阻止在沒有初始化的情況下實例化這些類。換句話說,我要確保,有些「初始化()」方法將總是子類的實例化後稱爲:

public abstract class Data { 

protected Parameter dataSource;  

    Data(parameter1){ 
    this.dataSource = parameter1; 

    loadData(); // should be called to initialise class fields and ensure correct work of other class methods 
    } 

protected abstract loadData(){ 
    ... //uses dataSource 
} 
} 

所以我決定在一個構造函數進行初始化。它的工作(現在我知道,這是一個非常糟糕的實踐),直到我創建了一個子類,其中initialize方法中使用一些額外的參數:

public class DataFromSpecificSources extends Data { 

private Parameter dataSource2; 

public DataFromSpecificSources(parameter1, parameter2){ 
    this.dataSource2 = parameter2; // I can't put it here because the constructor is not called yet 
    super(parameter1); // this, of course, will not work 
    } 

@Override 
private void loadData(){ 
    ... // uses both dataSource 1 and 2 
     // or just dataSource2 
    } 
} 

這當然是行不通的。我開始尋找右側圖案......當我閱讀問題的答案之前發佈,我決定用子類構造函數的工廠並限制能見度包:

我的解決辦法:

// factory ensures that loadData() method will be called 
public class MyDataFactory(){ 

public Data createSubClass(parameter1,parameter2){ 
    Data subClass; 

    if (parameter2 != null){ 
    subClass = new DataFromSpecificSources(parameter1, parameter2); 
    subClass.loadData(); 
    } else { 
    subClass = new AnotherSubClass(parameter1); 
    subClass.loadData() 
    } 

    return subClass; 
} 

} 


public abstract class Data { 

protected Parameter dataSource;  

    Data(parameter1){ 
    this.dataSource = parameter1; 
    } 

// I don't call it in constructor anymore - instead it's controlled within the factory 
protected abstract loadData(){ 
    ... //uses dataSource 
} 
} 



public class DataFromSpecificSources { 

private Parameter dataSource2; 

protected DataFromSpecificSources(){} 

// now this constructor is only visible within package (only for the factory in the same package) 
DataFromSpecificSources(parameter1, parameter2){ 
    super(parameter1); // it does not initialise data anymore 

    this.dataSource2 = parameter2; 
    } 

    @Override 
    protected void loadData(){ 
    ... // uses dataSources 1 and 2 
    } 
} 

現在工廠確保子類將被初始化(數據將被加載)並且在其他包中不允許實例化子類。其他類無法訪問子類的構造函數,並且被迫使用工廠來獲取子類的實例。

我只是想問,如果我的解決方案是正確的(邏輯上)和工廠方法的子類構造函數可見性限於包是正確的選擇在這裏?或者還有其他更有效的模式解決問題?!

+0

優質解決方案。然而,工廠是否需要成爲自己的班級?工廠方法可以在'Data'中作爲靜態方法嗎? –

+0

@JohnB由於他選擇的實現取決於參數,我會說不,工廠類是一個更好的選擇。 – Brian

回答

3

使用工廠絕對是邁向正確方向的一步。我看到的問題是,當你想添加第三個具有第三個參數的類時會發生什麼。現在你的工廠要麼必須有第二個超載的方法獲取第三個參數,否則你的所有代碼將不得不被重寫以提供第三個參數。此外,你迫使任何使用工廠的人爲第二個參數指定null,即使他們只想要單參數類....當你到達需要15個參數的類時如何記住哪個參數是哪個參數

解決方案是使用Builder模式。

public class MyDataBuilder(){ 
    private parameter1 = null; 
    private parameter2 = null; 

    public MyDataBuilder withParameter1(parameter1) { 
     this.parameter1 = parameter1; 
     return this; 
    } 

    public MyDataBuilder withParameter2(parameter2) { 
     this.parameter2 = parameter2; 
     return this; 
    } 

    public Data createSubClass(){ 
     Data subClass; 

     if (parameter2 != null){ 
      subClass = new DataFromSpecificSources(parameter1, parameter2); 
     } else { 
      subClass = new AnotherSubClass(parameter1); 
     } 
     subClass.loadData(); 
     return subClass; 
    } 

} 

現在創建數據實例代碼可以工作,像這樣:

Data data = new MyDataBuilder().withParameter1(param1).withParameter2(param2).create(); 

Data data = new MyDataBuilder().withParameter1(param1).create(); 

這代碼是面向未來的,因爲當你添加參數3 ...如果你需要的話,你甚至可以讓builder對參數3使用非空默認值。

你注意到接下來的事情是,你現在有一個包含所有必需的參數這個漂亮的Builder對象......所以現在你可以添加干將到Builder,只是通過Builder作爲構造函數的參數,例如

public class DataFromSpecificSources { 
    ... 

    DataFromSpecificSources(MyDataBuilder builder){ 
     ... 
    } 

    ... 

} 

所以,你現在幾乎有一個標準的構造函數簽名

現在對於一些Java具體的改進。我們可以讓建造者根本不需要知道子類!

使用DI框架,我們可以將實現接口/抽象類的類注入到Builder中,然後遍歷每個類,直到找到支持Builder實例配置的類。

的,窮人的DI框架是/META-INF/services合同和ServiceLoader類可用,因爲JRE 1.6(雖然因爲1.2的核心邏輯已經在Java中)

你建設者的創造方法,然後將看起來有點像

public Data create() { 
    for (DataFactory factory: ServiceLoader.load(DataFactory.class)) { 
     if (factory.canCreate(this)) { 
      Data result = factory.newInstance(this); 
      result.loadData(); 
      return result; 
     } 
    } 
    throw new IllegalStateException("not even the default instance supports this config"); 
} 

無論你想去極端是有問題的......但因爲你可能會在某個時間點在看別人的代碼時遇到它,它可能是一個很好的時間點出來給你現在。

哦,我們必須添加一個Factory類才能被ServiceLoader查找的原因是因爲ServiceLoader期望調用默認的構造函數,並且我們隱藏了默認的構造函數,所以我們使用Factory類來完成工作對我們而言,允許我們隱藏構造函數。

沒有什麼防止在數據類是靜態內部類(這使他們在他們所創建的類偉大的可見性),例如工廠類

public class UberData extends Data { 
    private UberData(MyDataBuilder config) { 
     ... 
    } 

    public static class Factory extends DataFactory { 
     protected Data create(MyDataBuilder config) { 
      return new UberData(config); 
     } 
     protected boolean canCreate(MyDataBuilder config) { 
      return config.hasFlanges() and config.getWidgetCount() < 7; 
     } 
    } 
} 

我們可以再META-INF/services/com.mypackage.DataFactory

com.mypackage.UberData.Factory 
com.mypackage.DataFromSpecificSources.Factory 
com.some.otherpackage.AnotherSubClass.Factory 

列出有關這種類型的解決方案的最佳位是它允許只是在運行時將這些實現到classpath中添加額外的實現...即非常鬆耦合

相關問題