9

ライブラリに抽象クラスがあります。このクラスの派生を適切に実装するのをできるだけ簡単にするようにしています。問題は、3つのステップのプロセスでオブジェクトを初期化する必要があることです。ファイルを取得し、いくつかの中間ステップを実行してから、ファイルを操作します。最初と最後のステップは、派生クラスに固有です。これは簡略化された例です。

abstract class Base
{
    // grabs a resource file specified by the implementing class
    protected abstract void InitilaizationStep1();

    // performs some simple-but-subtle boilerplate stuff
    private void InitilaizationStep2() { return; }

    // works with the resource file
    protected abstract void InitilaizationStep3();

    protected Base()
    {
        InitilaizationStep1();
        InitilaizationStep2();
        InitilaizationStep3();
    }
}

もちろん、問題はコンストラクターでの仮想メソッド呼び出しです。ライブラリの利用者は、派生クラスが完全に初期化されていることを期待できない場合、クラスを使用するときに制約を受けることになります。

Initialize()コンストラクターから保護されたメソッドにロジックを引き出すことはできますが、実装者はを呼び出す代わりStep1()Step3()直接呼び出すことができますInitialize()Step2()問題の核心は、スキップしても明らかなエラーが発生しないことです。特定の状況でのひどいパフォーマンス。

いずれにせよ、図書館の将来のユーザーが回避しなければならない深刻で自明ではない「落とし穴」があるように感じます。この種の初期化を実現するために使用する必要のある他の設計はありますか?

必要に応じて詳細を提供できます。私は問題を表現した最も単純な例を提供しようとしていました。

4

8 に答える 8

11

初期化用のテンプレートメソッドを使用して、派生クラスのインスタンスのインスタンス化と初期化を担当する抽象ファクトリを作成することを検討します。

例として:

public abstract class Widget
{
    protected abstract void InitializeStep1();
    protected abstract void InitializeStep2();
    protected abstract void InitializeStep3();

    protected internal void Initialize()
    {
        InitializeStep1();
        InitializeStep2();
        InitializeStep3();
    }

    protected Widget() { }
}

public static class WidgetFactory
{
    public static CreateWidget<T>() where T : Widget, new()
    {
        T newWidget = new T();
        newWidget.Initialize();
        return newWidget;
    }
}

// consumer code...
var someWidget = WidgetFactory.CreateWidget<DerivedWidget>();

このファクトリコードは劇的に改善される可能性があります-特にこの責任を処理するためにIoCコンテナを使用する場合は...

派生クラスを制御できない場合、呼び出すことができるパブリックコンストラクターを提供することを防ぐことはできないかもしれませんが、少なくとも、コンシューマーが順守できる使用パターンを確立することはできます。

クラスのユーザーが自分の足を撃つのを防ぐことが常に可能であるとは限りませんが、消費者がデザインに慣れたときにコードを正しく使用できるようにするインフラストラクチャを提供できます。

于 2009-07-20T19:18:38.363 に答える
4

これは、基本クラスではなく、任意のクラスのコンストラクターに配置するには多すぎます。Initializeそれを別の方法に織り込むことをお勧めします。

于 2009-07-20T19:06:42.770 に答える
1

編集:私は何らかの理由でC++についてこれに答えました。ごめん。C#の場合、Create()メソッドを使用しないことをお勧めします。コンストラクターを使用して、オブジェクトが最初から有効な状態にあることを確認してください。C#では、コンストラクターからの仮想呼び出しが可能です。期待される関数と事前条件および事後条件を注意深く文書化すれば、それらを使用しても問題ありません。コンストラクターからの仮想呼び出しを許可しないため、C++を初めて推測しました。

個々の初期化関数を作成しますprivateprivateとの両方にすることができますvirtualInitialize()次に、それらを正しい順序で呼び出すパブリックな非仮想関数を提供します。

オブジェクトの作成時にすべてが確実に行われるようにする場合は、コンストラクターを作成し、新しく作成されたオブジェクトを返す前に呼び出すprotected静的Create()関数をクラスで使用します。Initialize()

于 2009-07-20T19:09:36.117 に答える
1

多くの場合、初期化にはいくつかのプロパティの割り当てが含まれます。これらのプロパティ自体を作成しabstract、派生クラスでそれらをオーバーライドして、設定する基本コンストラクターに値を渡す代わりに、何らかの値を返すことができます。もちろん、このアイデアが適用できるかどうかは、特定のクラスの性質によって異なります。とにかく、コンストラクターにそれだけのコードがあるのは臭いです。

于 2009-07-20T19:09:50.830 に答える
1

一見すると、この種のロジックをこの初期化に依存するメソッドに移動することをお勧めします。何かのようなもの

public class Base
{
   private void Initialize()
   {
      // do whatever necessary to initialize
   }

   public void UseMe()
   {
      if (!_initialized) Initialize();
      // do work
   }
}
于 2009-07-20T19:12:14.210 に答える
1

ステップ1は「ファイルを取得する」ので、Initialize(IBaseFile)を使用して、ステップ1をスキップすることをお勧めします。この方法で、コンシューマーはファイルを好きなように取得できます。ファイルを返す抽象として「StepOneGetFile()」を提供することもできるので、必要に応じてそのように実装できます。

DerivedClass foo = DerivedClass();
foo.Initialize(StepOneGetFile('filepath'));
foo.DoWork();
于 2009-07-20T19:22:51.083 に答える
1

次のトリックを使用して、初期化が正しい順序で実行されることを確認できます。おそらく、初期化に依存する他のメソッド(DoActualWork )が基本クラスに実装されています。

抽象クラスベース
{{
    private bool _initialized;

    保護された抽象voidInitilaizationStep1();
    private void InitilaizationStep2(){return; }
    保護された抽象voidInitilaizationStep3();

    保護されたInitialize()
    {{
        //ここで仮想メソッドを呼び出しても安全です
        InitilaizationStep1();
        InitilaizationStep2();
        InitilaizationStep3();

        //オブジェクトを正しく初期化されたものとしてマークします
        _initialized = true;
    }

    public void DoActualWork()
    {{
        if(!_initialized)Initialize();
        Console.WriteLine( "私たちは確かに今初期化されています");
    }
}
于 2009-07-20T19:35:40.137 に答える
0

私はこれをしません。私は一般的に、コンストラクターで「実際の」作業を行うことは、将来的には悪い考えになることに気づきます。

少なくとも、ファイルからデータをロードするための別の方法があります。それをさらに一歩進めて、ファイルからオブジェクトの1つを構築するための別のオブジェクトを用意し、「ディスクからのロード」とオブジェクトのメモリ内操作の問題を分離するという議論をすることができます。

于 2009-07-21T05:46:33.263 に答える