8

私は.NETベースのアプリケーションに取り組んでおり、コアアプリケーションクラスの一部は静的メソッドのみで設計されています。

使用例:

// static access.
Parameters.GetValue("DefaultTimeout");

// static access.
Logger.Log("This is an important message!");

これらの静的メソッドを使用するコードはすでに存在するため、この「インターフェース」は変更できません。

これらのクラスは現在、インターフェースを実装していません。これらのクラスの実際の実装をそれらのインターフェースから分離できるようにしたいと思います。

このリファクタリングの理由は、これらのオブジェクトがAppDomainの境界を越えて使用されるためです。非メインアプリドメインでデフォルトの実装の代わりに他の実装を呼び出す「プロキシ」オブジェクトを挿入できるようにしたいと思います。

要約すると、私の質問は次のとおりです。

  1. インターフェイスベースの設計への静的のみのアクセスでオブジェクトを簡単に変換して、必要に応じて実装を置き換えることができるようにするにはどうすればよいですか(ただし、静的アクセスは維持します)。

  2. リファクタリングされたら、デフォルト以外の実装の実際の注入はどのように/いつ行われるべきですか?

4

3 に答える 3

12

免責事項:次の提案は、呼び出し側を変更しないことの重要性に基づいています。私はそれが最良の選択肢だと言っているのではなく、それが適切だと思うだけです。

実装の切断

静的メンバーにインターフェイスを設定する方法はないため、呼び出し元のコードを変更したくない場合は、静的メンバーを残しておく必要があります。とは言うものの、静的クラスにインターフェイスをラップさせるだけなので、静的クラス自体には実装がなく、すべての呼び出しがインターフェイスに委任されます。

これはすべて、静的クラスとそれを呼び出すコードをそのままにしておくことができることを意味します。これは、静的クラスをインターフェース(またはコントラクト)として扱うのと似ていますが、状況に基づいて実装を内部的にスワップアウトします。

また、インターフェイスは呼び出し元のコードの期待に準拠する必要がないため、インターフェイスが静的クラスとは異なるシグネチャを持つことができることも意味します。基本的に、静的クラスは一種のブリッジになります。

実装の注入

つまり、このインターフェイスの特定の実装を解決するには、静的コンストラクターを使用します。

スタティックは通常AppDomainごとに(で装飾されていない限りThreadStaticAttribute、AppDomain /スレッドごとに)、現在のAppDomainに基づいて現在の場所と必要な実装を決定できます(スタティックがAppDomainで最初に使用されるたびに、スタティックコンストラクターが呼び出されます) 。これは、一度構築されると、その特定の静的クラスのラップされた実装がAppDomainの期間中残ることを意味します(ただし、実装をフラッシュするメソッドを実装することはできます)。

クロスAppDomain呼び出し

これを担当するコードは、静的クラスに含めることも、インターフェイス実装の1つをAppDomainタイプのプロキシマネージャーにすることもできます。クロスAppDomain呼び出しのタイプはすべて、を継承する必要がありMarshalByRefObjectます。

http://msdn.microsoft.com/en-us/library/ms173139.aspx

別のAppDomainのタイプのCreateInstance

クロスアプリドメイン呼び出しを行う最も簡単な方法は?

サンプルアプリケーション

これをコピーして新しいコンソールアプリケーションに貼り付けることができるはずです。これが行っているのは、デフォルトのAppDomainの実装と、ユーザーが作成したAppDomainの実装を登録することです。デフォルトでは、(他のAppDomainに)インターフェイスのリモート実装を作成するだけです。「AppDomainごとの静的」の考え方を示すために、リモート実装はデフォルト以外のドメインのさらに別の実装に委任します。

実装はその場で変更できます。変更する必要があるのは、静的クラスコンストラクター(選択する実装を決定するため)だけです。Mainメソッド(この場合は呼び出しコード)を変更する必要がないことに注意してください。

using System;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
        Console.WriteLine(Parameters.GetValue(""));
        Console.Read();
    }
}

static class Parameters
{
    private static IParameterProvider _provider;

    static Parameters()
    {
        if (AppDomain.CurrentDomain.IsDefaultAppDomain())
        {
            _provider = new ParameterProviderProxy(AppDomain.CreateDomain(Guid.NewGuid().ToString()));
        }
        else
        {
            // Breakpoint here to see the non-default AppDomain pick an implementation.
            _provider = new NonDefaultParameterProvider();
        }
    }

    public static object GetValue(string name)
    {
        return _provider.GetValue(name);
    }
}

interface IParameterProvider
{
    object GetValue(string name);
}

class CrossDomainParameterProvider : MarshalByRefObject, IParameterProvider
{
    public object GetValue(string name)
    {
        return Parameters.GetValue(name);
    }
}

class NonDefaultParameterProvider : IParameterProvider
{
    public object GetValue(string name)
    {
        return AppDomain.CurrentDomain.FriendlyName;
    }
}

class ParameterProviderProxy : IParameterProvider
{
    private IParameterProvider _remoteProvider;

    public ParameterProviderProxy(AppDomain containingDomain)
    {
        _remoteProvider = (CrossDomainParameterProvider)containingDomain.CreateInstanceAndUnwrap(
            Assembly.GetExecutingAssembly().FullName,
            typeof(CrossDomainParameterProvider).FullName);
    }

    public object GetValue(string name)
    {
        return _remoteProvider.GetValue(name);
    }
}

寿命に関する注記

静的クラスのリファクタリングを管理する際の主な問題の1つは、通常、クライアントコードの変更ではなく(これは多くのリファクタリングツールでサポートされており、安全に実行するための手法があるため)、物体。インスタンスオブジェクトは生きている参照に依存します(そうでない場合はガベージコレクションされます)。これらは通常、パブリック静的メンバーのどこかに保持することで「簡単にアクセス」できますが、通常、これは最初にリファクタリングすることで回避しようとしているものです。

呼び出し元のコードを静的クラスにアタッチしたままにしておくため、この懸念について心配する必要はないようです。したがって、寿命は同じままです。

于 2012-05-27T17:10:49.303 に答える
6

すべての静的メソッドに対して、インスタンス1を作成します。任意の実装を割り当てることができる静的シングルトン変数を追加します。静的メソッドが静的シングルトンのインスタンスメソッドを呼び出すようにします。

これにより、実行時に実装を交換できますが、同時にフックできる実装は1つだけです。

既存のコードを変更する必要はありません。

于 2012-05-27T17:01:51.603 に答える
3

静的クラスはシングルトンオブジェクトに変換できます。

シングルトンオブジェクトはインターフェイスをサポートします。

インターフェイスは、さまざまな実装に使用できます。

(1)問題の定義。

静的メンバーを持つクラスがあるとします。

-

StringsClass.cs

-

namespace Libraries
{
  public static class StringsClass
  {

    public static string UppercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to uppercase,
      // and output stored in "Result"

    return Result;
    } // string UppercaseCopy(...)

    public static string LowercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to lowercase,
      // and output stored in "Result"

      return Result;
    } // string LowercaseCopy(...)

    public static string ReverseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is reversed,
      // and output stored in "Result"

      return Result;
    } // string ReverseCopy(...)  

  } // class StringsClass

} // namespace Libraries

-

そして、そのクラスからのその静的要素を使用するいくつかのコード。

-

StringsLibraryUser.cs

-

using Libraries;

namespace MyApp
{
  public class AnyClass
  {
    public void AnyMethod()
    {
      string Example = "HELLO EARTH";
      string AnotherExample = StringsClass.LowercaseCopy(Example);
    } // void AnyMethod(...)  

  } // class AnyClass

} // namespace MyApp

-

(2)まず、クラスを非静的クラスに変換します。

-

StringsClass.cs

-

namespace Libraries
{
  public class StringsClass
  {

    public string UppercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to uppercase,
      // and output stored in "Result"

    return Result;
    } // string UppercaseCopy(...)

    public string LowercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to lowercase,
      // and output stored in "Result"

      return Result;
    } // string LowercaseCopy(...)

    public string ReverseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is reversed,
      // and output stored in "Result"

      return Result;
    } // string ReverseCopy(...)  

  } // class StringsClass

} // namespace Libraries

-

(3)allowクラスが単一のオブジェクトを処理するコードを追加します。

-

StringsClass.cs

-

namespace Libraries
{
  public class StringsClass
  {
    private static Singleton instance = null;

    private Singleton()
    {
      // ...
    }

    public static synchronized Singleton getInstance()
    {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public string UppercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to uppercase,
      // and output stored in "Result"

    return Result;
    } // string UppercaseCopy(...)

    public string LowercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to lowercase,
      // and output stored in "Result"

      return Result;
    } // string LowercaseCopy(...)

    public string ReverseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is reversed,
      // and output stored in "Result"

      return Result;
    } // string ReverseCopy(...)  

  } // class StringsClass

} // namespace Libraries

-

(4)クラスを呼び出すコードは、シングルトンの参照を追加する必要があります。

-

StringsLibraryUser.cs

-

using Libraries;

namespace MyApp
{
  public class AnyClass
  {
    public void AnyMethod()
    {
      string Example = "HELLO EARTH";
      string AnotherExample = StringsClass.getInstance().LowercaseCopy(Example);
    } // void AnyMethod(...)  

  } // class AnyClass

} // namespace MyApp

-

(5)前の静的クラスと同様の宣言を使用してインターフェースを定義し、シングルトンがそのインターフェースを実装できるようにします。インターフェイス宣言でシングルトンメンバーを省略します

-

StringsClass.cs

-

namespace Libraries
{
  public interface StringsInterface
  {
    string UppercaseCopy(string Value);  
    string LowercaseCopy(string Value);  
    string ReverseCopy(string Value);   
  } // interface StringsInterface

  public class StringsClass: StringsInterface
  {
    private static Singleton instance = null;

    private Singleton()
    {
      // ...
    }

    public static synchronized Singleton getInstance()
    {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public string UppercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to uppercase,
      // and output stored in "Result"

    return Result;
    } // string UppercaseCopy(...)

    public string LowercaseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is converted to lowercase,
      // and output stored in "Result"

      return Result;
    } // string LowercaseCopy(...)

    public string ReverseCopy(string Value)
    {
      string Result = "";

      // code where "Value" is reversed,
      // and output stored in "Result"

      return Result;
    } // string ReverseCopy(...)  

  } // class StringsClass

} // namespace Libraries

-

(6)静的メソッドを含む以前のクラスであるシングルトンを使用しているコードでは、インターフェイスのシングルトンを置き換えます。

-

StringsLibraryUser.cs

-

using Libraries;

namespace MyApp
{
  public class AnyClass
  {
    public StringsInterface StringsHelper = StringsClass.getInstance().LowercaseCopy(Example);

    public void AnyMethod()
    {
      string Example = "HELLO EARTH";
      string AnotherExample = StringsHelper;
    } // void AnyMethod(...)  

  } // class AnyClass

} // namespace MyApp

-

これで、実装が異なる同じ宣言をサポートする他のクラスを追加できます。

乾杯。

-

于 2012-05-27T20:03:54.847 に答える