web-dev-qa-db-ja.com

依存性注入で使用する静的クラスのリファクタリング

コードでは、静的メソッドを持つアンマネージライブラリを使用する必要があります。コードの依存関係としてライブラリ操作を導入したいと思います。また、静的メソッドのほかに、ライブラリには初期化メソッドと設定メソッドがあり、どちらもグローバルです。したがって、これをインスタンスクラスでラップすることはできません。これは、1つのインスタンスが設定を変更すると、他のすべてのインスタンスが影響を受け、1つのインスタンスが初期化されると、他のすべてのインスタンスが再初期化されるためです。

シングルトンクラスとして導入することを考えました。このようにすると、インスタンスクラスになりますが、インスタンスは1つしかないため、設定の変更や初期化について心配する必要はありません。このアプローチについてどう思いますか?私は依存性注入パターンにかなり慣れていませんが、シングルトンパターンが良い解決策であるかどうかわかりませんか?同様のケースに対するあなたの解決策は何でしょうか?

編集:初期化にもパラメータが必要なので、メソッド呼び出しをロックして、呼び出されるたびに再初期化して設定を変更することはできません。

編集2:いくつかのメソッドのシグネチャは次のとおりです。

public static void Initialize(int someParameter)
// Parameter can only be changed by re-initalization which
// will reset all the settings back to their default values.

public static float[] Method1(int someNumber, float[] someArray)

public static void ChangeSetting(string settingName, int settingValue)
14
hattenn

起動時に一度だけ設定する必要がある場合は、独自の静的コンストラクターで静的クラスのすべての初期化を行う非静的ラッパークラスを作成することをお勧めします。そうすれば、それが1回だけ発生することを保証できます。

public class MyWrapper
{
    public MyWrapper()
    {
        // Do any necessary instance initialization here
    }

    static MyWrapper()
    {
        UnManagedStaticClass.Initialize();
        UnManagedStaticClass.Settings = ...;
    }

    public void Method1()
    {
        UnManagedStaticClass.Method1();
    }
}

ただし、呼び出すたびに設定を変更する必要があり、インスタンスをスレッドセーフにしたい場合は、静的オブジェクトをロックして、静的設定が誤って上書きされないようにすることをお勧めします。別のスレッドでまだ使用中:

public class MyWrapper
{
    public MyWrapper()
    {
        // Do any necessary instance initialization here
    }

    static MyWrapper()
    {
        UnManagedStaticClass.Initialize();
    }

    static object lockRoot = new Object();

    public void Method1()
    {
        lock (lockRoot)
        {
            UnManagedStaticClass.Settings = ...;
            UnManagedStaticClass.Method1();
        }
    }
}   

クラスのインスタンスコンストラクターに初期化パラメーターを渡す必要がある場合は、静的フラグフィールドを設定することでそれを行うこともできます。

public class MyWrapper
{
    public MyWrapper(InitParameters p)
    {
        lock (lockRoot)
        {
            if (!initialized)
            {
                UnManagedStaticClass.Initialize(p);
                initialized = true;
            }
        }
    }

    static bool initialized = false;
    static object lockRoot = new Object();

    public void Method1()
    {
        lock (lockRoot)
        {
            UnManagedStaticClass.Settings = ...;
            UnManagedStaticClass.Method1();
        }
    }
}

毎回再初期化する必要があるが、再初期化が遅すぎるためにパフォーマンスが心配な場合、(恐ろしいシングルトン以外の)他の唯一のオプションは、再初期化する必要があるかどうかを自動検出することです。必要な場合にのみ実行してください。少なくとも、それが発生するのは、2つのスレッドが2つの異なるインスタンスを同時に使用している場合のみです。あなたはこのようにそれを行うことができます:

public class MyWrapper
{
    public MyWrapper(InitParameters initParameters, Settings settings)
    {
        this.initParameters = initParameters;
        this.settings = settings;
    }

    private InitParameters initParameters;
    private Settings settings;
    static MyWrapper currentOwnerInstance;
    static object lockRoot = new Object();

    private void InitializeIfNecessary()
    {
        if (currentOwnerInstance != this)
        {
            currentOwnerInstance = this;
            UnManagedStaticClass.Initialize(initParameters);
            UnManagedStaticClass.Settings = settings;
        }
    }

    public void Method1()
    {
        lock (lockRoot)
        {
            InitializeIfNecessary();
            UnManagedStaticClass.Method1();
        }
    }
}
10
Steven Doggart

ステートレスサービスクラスを使用し、メソッド呼び出しごとに静的クラスの状態情報を渡します。クラスの詳細を知らなくても、c#静的クラスを使用した別の例を示します。

public static class LegacyCode
{
    public static void Initialize(int p1, string p2)
    {
        //some static state
    }
    public static void ChangeSettings(bool p3, double p4)
    {
        //some static state
    }
    public static void DoSomething(string someOtherParam)
    {
        //execute based on some static state
    }
}

public class LegacyCodeFacadeService
{
    public void PerformLegacyCodeActivity(LegacyCodeState state, LegacyCodeParams legacyParams)
    {
        lock (_lockObject)
        {
            LegacyCode.Initialize(state.P1, state.P2);
            LegacyCode.ChangeSettings(state.P3, state.P4);
            LegacyCode.DoSomething(legacyParams.SomeOtherParam);
            //do something to reset state, perhaps
        }
    }
}

空白を少し埋める必要がありますが、うまくいけばアイデアが浮かびます。重要なのは、静的オブジェクトに必要最小限の時間状態を設定し、その間ずっと静的オブジェクトへのアクセスをロックして、他の呼び出し元がグローバルな状態変化の影響を受けないようにすることです。このクラスを使用するには、このクラスの新しいインスタンスを作成する必要があります。これにより、完全に注入可能でテスト可能になります(簡潔にするためにスキップしたインターフェイスを抽出する手順を除く)。

ここでの実装には多くのオプションがあります。たとえば、LegacyCodeStateを大幅に変更する必要があるが、特定の状態の数が少ない場合は、それらの状態を管理する作業を行うオーバーロードが発生する可能性があります。

[〜#〜]編集[〜#〜]

これは多くの点でシングルトンよりも望ましいですが、最も重要なのは、累積してグローバル状態に結合できないことです。これにより、静的クラスへの唯一のエントリポイントである場合、グローバル状態が非グローバル状態に変わります。 。ただし、シングルトンが必要になった場合は、ここでコンストラクターをカプセル化することで簡単に切り替えることができます。

public class LegacyCodeFacadeService
{
    private LegacyCodeFacadeService() { }

    public static LegacyCodeFacadeService GetInstance()
    {
        //now we can change lifestyle management strategies later, if needed
        return new LegacyCodeFacadeService();
    }

    public void PerformLegacyCodeActivity(LegacyCodeState state, LegacyCodeParams legacyParams)
    {
        lock (_lockObject)
        {
            LegacyCode.Initialize(state.P1, state.P2);
            LegacyCode.ChangeSettings(state.P3, state.P4);
            LegacyCode.DoSomething(legacyParams.SomeOtherParam);
            //do something to reset state, perhaps
        }
    }
}
1
tallseth