在不调用所需初始化方法的情况下阻止子类实例化的正确方法?

时间:2012-09-14 15:04:04

标签: java design-patterns inheritance override factory

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

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

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?

问题:我想要的子类只在初始化方法上有所不同。但是,我还希望在没有初始化的情况下阻止实例化这些类。换句话说,我想确保在实例化子类之后调用一些“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
 }
}

所以我决定在构造函数上执行初始化。它工作(现在我知道这是一个非常糟糕的做法),直到我创建了一个子类,其中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
  }
}

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

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

1 个答案:

答案 0 :(得分:3)

使用工厂绝对是朝着正确方向迈出的一步。我看到的问题是当你想要添加第三个带第三个参数的类时会发生什么。现在你的工厂要么必须有第二个重载createSubClass方法获取第三个参数,要么所有代码​​都必须被重写以提供第三个参数。另外,你强迫所有使用Factory的人为第二个参数指定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();

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

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

public class DataFromSpecificSources {
   ...

   DataFromSpecificSources(MyDataBuilder builder){
       ...
   }

   ...

}

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

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

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

穷人的DI框架是/META-INF/services合同和自{JRE 1.6以来可用的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");
}

你是否想要走到这个极端是值得怀疑的......但是因为你在某个时候看到其他人的代码时可能会遇到它,现在可能是向你指出它的好时机。 / p>

哦,我们必须添加一个要由ServiceLoader查找的Factory类的原因是因为ServiceLoader期望调用默认构造函数,并且我们隐藏了默认构造函数,因此我们使用Factory类来完成工作我们并允许我们隐藏构造函数。

没有什么可以阻止Factory类成为Data类中的静态内部类(这使它们对它们正在创建的类具有很高的可见性),例如。

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

关于这种类型的解决方案最好的一点是,它允许添加其他实现,只需在运行时将这些实现添加到类路径中......即非常松散的耦合