3

初心者のプログラマーが自分のソリューションが正しいかどうかを理解するのを誰かが助けることができますか?

私の質問は、次の 2 つに似ています。

コンストラクターでのオーバーライド可能なメソッド呼び出しの何が問題になっていますか?

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

ファクトリにより、サブクラスが初期化され (データがロードされます)、サブクラスのインスタンス化が他のパッケージで許可されないことが保証されます。他のクラスはサブクラスのコンストラクタにアクセスできず、ファクトリを使用してサブクラスのインスタンスを取得する必要があります。

私のソリューションが(論理的に)正しいかどうかを尋ねたかっただけで、サブクラスコンストラクターの可視性がパッケージに制限されたFactoryメソッドがここで正しい選択ですか?! または、問題を解決する他のより効果的なパターンはありますか?!

4

1 に答える 1

3

ファクトリを使用することは、間違いなく正しい方向への一歩です。私が見ている問題は、3 番目のパラメーターを取る 3 番目のクラスを追加するときに何が起こるかということです。ここで、Factory に 3 番目のパラメーターを受け取る 2 番目のオーバーロードされたcreateSubClassメソッドが必要になるか、3 番目のパラメーターを提供するためにすべてのコードを書き直す必要があります。さらに、Factory を使用しているすべての人にnull、1 つのパラメーター クラスだけが必要な場合でも、2 番目のパラメーターを指定するように強制しています。

これに対する解決策は、代わりに 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();

そして、そのコードは、parameter3 を追加するときに将来的に保証されます...必要に応じて、parameter3 の null 以外のデフォルトをビルダーに設定することもできます。

次に気付くのは、必要なすべてのパラメーターを含むこの素敵な Builder オブジェクトがあることです...これで、Builder にゲッターを追加して、Builder をコンストラクターパラメーターとして渡すことができます。

public class DataFromSpecificSources {
   ...

   DataFromSpecificSources(MyDataBuilder builder){
       ...
   }

   ...

}

これで、標準のコンストラクター シグネチャがほぼ完成しました。

次に、Java 固有の改善について説明します。ビルダーがサブクラスについてまったく知る必要がないようにすることができます!

DI フレームワークを使用すると、インターフェイス/抽象クラスを実装するDataクラスを Builder に挿入し、Builder インスタンスの構成をサポートするクラスが見つかるまで各クラスを反復処理できます。

貧乏人の DI フレームワークは、 JRE 1.6 以降で利用可能な/META-INF/servicesコントラクトとServiceLoaderクラスです (ただし、コア ロジックは 1.2 以降 Java にあります)。

ビルダーの create メソッドは次のようになります。

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");
}

そこまで行きたいかどうかは疑問ですが、他の人のコードを見ていると、ある時点でそれに遭遇するかもしれないので、今指摘するのはおそらく良い時期です.

ああ、ServiceLoader によって検索される Factory クラスを追加する必要がある理由は、ServiceLoader がデフォルトのコンストラクターを呼び出すことを想定しているためです。デフォルトのコンストラクターを非表示にしたため、Factory クラスを使用して作業を行い、許可します。コンストラクターを隠しておきます。

Factory クラスが Data クラスの static 内部クラスになることを妨げるものは何もありません (これにより、作成しているクラスの可視性が大幅に向上します)。

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 に答える