11

アプリケーションのブラウザ実装クラスを定義しているとしましょう:

class InternetExplorerBrowser : IBrowser {
    private readonly string executablePath = @"C:\Program Files\...\...\ie.exe";

    ...code that uses executablePath
}

executablePathこれは、データがそれを使用するコードの近くにあるため、一見すると良いアイデアのように見えるかもしれません。

問題は、外国語の OSexecutablePathを搭載した別のコンピューターでこの同じアプリケーションを実行しようとしたときに発生します。値が異なります。

AppSettings シングルトン クラス (またはそれに相当するもの) を使用してこれを解決できますが、私のクラスが実際にこの AppSettings クラスに依存していることを誰も知りません (これは DI アイデアに反します)。単体テストにも問題が生じる可能性があります。

executablePathコンストラクターを介して渡されることで、両方の問題を解決できました。

class InternetExplorerBrowser : IBrowser {
    private readonly string executablePath;

    public InternetExplorerBrowser(string executablePath) {
        this.executablePath = executablePath;
    }
}

しかし、これは私のComposition Root(必要なすべてのクラスの配線を行うスタートアップメソッド)で問題を引き起こします.

class CompositionRoot {
    public void Run() {
        ClassA classA = new ClassA();

        string ieSetting1 = "C:\asdapo\poka\poskdaposka.exe";
        string ieSetting2 = "IE_SETTING_ABC";
        string ieSetting3 = "lol.bmp";

        ClassB classB = new ClassB(ieSetting1);
        ClassC classC = new ClassC(B, ieSetting2, ieSetting3);

        ...
    }
}

これは簡単に大きな混乱になります。

代わりにフォームのインターフェースを渡すことで、この問題を回避できます

interface IAppSettings {
    object GetData(string name);
}

何らかの設定が必要なすべてのクラスに。次に、これを、すべての設定が埋め込まれた通常のクラスとして実装するか、XML ファイルからデータを読み取るクラスとして実装します。これを行う場合、システム全体の一般的な AppSettings クラス インスタンスを用意する必要がありますか?それとも、AppSettings クラスを必要とする各クラスに関連付ける必要がありますか? それは確かに少しやり過ぎのようです。また、すべてのアプリケーション設定を同じ場所に置くことで、プログラムを別のプラットフォームに移行しようとするときに必要なすべての変更を簡単に確認できます。

この一般的な状況にアプローチする最善の方法は何でしょうか?

編集:

またIAppSettings、すべての設定がハードコードされた を使用する場合はどうでしょうか。

interface IAppSettings {
    string IE_ExecutablePath { get; }
    int IE_Version { get; }
    ...
}

これにより、コンパイル時の型安全性が可能になります。インターフェイス/具象クラスが大きくなりすぎた場合は、フォームの他の小さなインターフェイスを作成できますIMyClassXAppSettings。中規模/大規模プロジェクトでは負担が大きすぎますか?

また、AOP と、分野横断的な懸念に対処するその利点についても読んだことがあります (これはその 1 つだと思います)。この問題の解決策も提供できませんでしたか? たぶん、次のような変数のタグ付け:

class InternetExplorerBrowser : IBrowser {
    [AppSetting] string executablePath;
    [AppSetting] int ieVersion;

    ...code that uses executablePath
}

次に、プロジェクトをコンパイルするときに、コンパイル時の安全性も確保します (データを織り込むコードを実際に実装したことをコンパイラーにチェックさせます)。もちろん、これにより、API がこの特定のアスペクトに結び付けられます。

4

2 に答える 2

15

個々のクラスはIAppSettings、可能な限りインフラから解放されるべきです。依存性注入の技術は、懸念事項の因数分解にあります。IMyClassXAppSettings[AppSetting]executablePath

Autofacを使用してこの正確なパターンを実装しました。これには Ninject に似たモジュールがあり、同様のコードになるはずです(質問にはNinjectについて言及していませんが、OPはコメントで言及しています)。

モジュールは、アプリケーションをサブシステム別に編成します。モジュールは、サブシステムの構成可能な要素を公開します。

public class BrowserModule : Module
{
    private readonly string _executablePath;

    public BrowserModule(string executablePath)
    {
        _executablePath = executablePath;
    }

    public override void Load(ContainerBuilder builder)
    {
        builder
            .Register(c => new InternetExplorerBrowser(_executablePath))
            .As<IBrowser>()
            .InstancePerDependency();
    }
}

これにより、コンポジション ルートに同じ問題が残ります: の値を提供する必要がありますexecutablePath。構成のスープを避けるために、構成設定を読み取って に渡す自己完結型のモジュールを作成できますBrowserModule

public class ConfiguredBrowserModule : Module
{
    public override void Load(ContainerBuilder builder)
    {
        var executablePath = ConfigurationManager.AppSettings["ExecutablePath"];

        builder.RegisterModule(new BrowserModule(executablePath));
    }
}

の代わりにカスタム構成セクションを使用することを検討できますAppSettings。変更はモジュールにローカライズされます。

public class BrowserSection : ConfigurationSection
{
    [ConfigurationProperty("executablePath")]
    public string ExecutablePath
    {
        get { return (string) this["executablePath"]; }
        set { this["executablePath"] = value; }
    }
}

public class ConfiguredBrowserModule : Module
{
    public override void Load(ContainerBuilder builder)
    {
        var section = (BrowserSection) ConfigurationManager.GetSection("myApp.browser");

        if(section == null)
        {
            section = new BrowserSection();
        }

        builder.RegisterModule(new BrowserModule(section.ExecutablePath));
    }
}

各サブシステムには、1 つの場所で読み取られる独立した構成があるため、これは適切なパターンです。ここでの唯一の利点は、より明白な意図です。ただし、string非値または複雑なスキーマの場合は、面倒な作業を任せることができSystem.Configurationます。

于 2010-08-28T20:40:09.760 に答える
2

最後のオプションを使用します-インターフェイスに準拠するオブジェクトを渡しIAppSettingsます。実際、私は最近、いくつかの単体テストを整理するために職場でそのリファクタリングを実行しましたが、うまく機能しました。ただし、そのプロジェクトの設定に依存するクラスはほとんどありませんでした。

設定クラスのインスタンスを 1 つ作成し、それに依存するすべてのものに渡します。それには根本的な問題は見られません。

ただし、これについてはすでに考えており、設定に依存するクラスがたくさんある場合にどのように苦労するかを見たと思います。

これが問題である場合は、ninject などの依存性注入フレームワークを使用して回避できます ( ninjectのようなプロジェクトを既に知っている場合は申し訳ありません。 github のuse ninjectセクションは学習するのに適した場所です)。

IAppSettingsninject を使用すると、メイン プロジェクトに対して、依存関係のあるすべてのクラスで、ベース クラスのシングルトン インスタンスを使用するように宣言できますAppSettings。コンストラクターに明示的に渡す必要はありません。

MockAppSettings次に、使用される場所のインスタンスを使用するIAppSettingsことを指定するか、単に明示的にモック オブジェクトを直接渡すことによって、単体テスト用にシステムを別の方法でセットアップできます。

私はあなたの質問の要点を正しく理解し、私が助けてくれたことを願っています - あなたはすでにあなたが何をしているのか知っているように聞こえます:)

于 2010-08-28T11:23:01.667 に答える