3

有人可以帮助新手程序员了解他的解决方案是否正确吗?

我的问题类似于以下两个:

构造函数中可覆盖的方法调用有什么问题?

C#中的工厂模式:如何确保对象实例只能由工厂类创建?

问题:我想拥有仅在初始化方法上有所不同的子类。但是,我还想防止在没有初始化的情况下实例化这些类。换句话说,我想确保在子类实例化之后总是会调用一些“initialize()”方法:

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
 }
}

所以我决定在构造函数上执行初始化。它有效(现在我知道这是一个非常糟糕的做法),直到我创建了一个子类,其中初始化方法使用了一些额外的参数:

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
  }
}

现在工厂确保子类将被初始化(将加载数据)并且在其他包中不允许子类的实例化。其他类无法访问子类的构造函数,并且被迫使用工厂来获取子类的实例。

我只是想问一下我的解决方案是否正确(逻辑上),并且子类构造函数可见性仅限于包的工厂方法在这里是正确的选择吗?!还是有其他更有效的模式来解决这个问题?!

4

1 回答 1

3

使用工厂绝对是朝着正确方向迈出的一步。我看到的问题是,当您想要添加带有第三个参数的第三个类时会发生什么。现在你的工厂要么必须有第二个重载createSubClass方法来获取第三个参数,要么你的所有代码都必须重写以提供第三个参数。null此外,即使他们只需要单个参数类,您也强迫任何使用 Factory 的人指定第二个参数......当您到达采用 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 data = new MyDataBuilder().withParameter1(param1).withParameter2(param2).create();

或者

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

当您添加参数 3 时,该代码是面向未来的......如果您需要,您甚至可以让构建器对参数 3 具有非空默认值。

接下来您注意到的是,您现在有了一个包含所有必需参数的漂亮 Builder 对象......所以现在您可以将 getter 添加到 Builder 并将 Builder 作为构造函数参数传递,例如

public class DataFromSpecificSources {
   ...

   DataFromSpecificSources(MyDataBuilder builder){
       ...
   }

   ...

}

这样你现在几乎有了一个标准的构造函数签名

现在进行一些 Java 特定的改进。我们可以让构建者根本不需要知道子类!

使用 DI 框架,我们可以将实现Data接口/抽象类的类注入到 Builder 中,然后遍历每个类,直到找到支持 Builder 实例配置的类。

穷人的 DI 框架是自 JRE 1.6 以来可用的/META-INF/services契约和ServiceLoader类(尽管核心逻辑自 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

这种类型的解决方案最好的一点是它允许通过在运行时将这些实现添加到类路径来添加额外的实现......即非常松散的耦合

于 2012-09-14T22:59:50.757 回答