14

抽象基本クラスから Service Locator を削除しようとしていますが、何に置き換えればよいかわかりません。ここに私が持っているものの疑似例があります:

public abstract class MyController : Controller
{
    protected IKernel kernel;
    public MyController(IKernel kernel) { this.kernel = kernel); }

    protected void DoActions(Type[] types)
    {

        MySpecialResolver resolver = new MySpecialResolver(kernel);
        foreach(var type in types)
        {
            IMyServiceInterface instance = resolver.Get(type);
            instance.DoAction();
        }
    }
}

これに関する問題MySpecialResolverは、派生クラスのインスタンシエーターが、カーネルが例外をスローしないようにするために必要なバインディングを認識していないことです。

ここからどのタイプを解決しなければならないかわからないため、これは本質的に扱いにくいかもしれません。派生クラスはtypesパラメーターの作成を担当しますが、どこにもハードコーディングされていません。(型は、派生クラスの構成階層の奥深くにある属性の存在に基づいています。)

遅延読み込みデリゲートでこれを修正しようとしましたが、これまでのところ明確な解決策は思いつきませんでした。

アップデート

ここには実際には 2 つの問題があります。1 つは、IoC コンテナーがコントローラーに渡され、サービス ロケーターとして機能することです。これは簡単に削除できます。あらゆる種類の手法を使用して、コール スタックの上下に位置を移動できます。

2 番目の問題は難しい問題です。要件が実行時まで公開されない場合、コントローラーに必要なサービスがあることをどのように保証できるでしょうか。最初から明らかだったはずです。サービスロケータの状態またはコレクションの内容に常に依存します。この特定のケースでは、静的に型付けされた依存関係に関するこの記事で説明されている問題をいくらいじっても解決しません。私が最終的にやろうとしているのは、Lazy 配列をコントローラー コンストラクターに渡し、必要な依存関係が欠落している場合は例外をスローすることだと思います。

4

3 に答える 3

4

@chrisichris と @Mark Seemann に同意します。

コントローラーからカーネルを切り離します。コントローラーが IoC コンテナーへの依存を削除し、リゾルバーが IoC コンテナーを心配する唯一の項目になるように、リゾルバーの構成を少し変更します。

次に、リゾルバーがコントローラーのコンストラクターに渡されるようにします。これにより、コントローラーをよりテストしやすくなります。

例えば:

public interface IMyServiceResolver
{
    List<IMyServiceInterface> Resolve(Type[] types);
}

public class NinjectMyServiceResolver : IMyServiceResolver
{
    private IKernal container = null;

    public NinjectMyServiceResolver(IKernal container)
    {
        this.container = container;
    }

    public List<IMyServiceInterface> Resolve(Type[] types)
    {
        List<IMyServiceInterface> services = new List<IMyServiceInterface>();

        foreach(var type in types)
        {
            IMyServiceInterface instance = container.Get(type);
            services.Add(instance);
        }

        return services;
    }
}

public abstract class MyController : Controller
{
    private IMyServiceResolver resolver = null;

    public MyController(IMyServiceResolver resolver) 
    { 
        this.resolver = resolver;
    }

    protected void DoActions(Type[] types)
    {
        var services = resolver.Resolve(types);

        foreach(var service in services)
        {
            service.DoAction();
        }
    }
}

これで、コントローラーが特定の IoC コンテナーに結合されなくなりました。また、リゾルバーをモックでき、テストに IC コンテナーをまったく必要としないため、コントローラーのテストがはるかに容易になります。

または、コントローラーがインスタンス化されたときに制御できない場合は、少し変更できます。

public abstract class MyController : Controller
{
    private static IMyServiceResolver resolver = null;

    public static InitializeResolver(IMyServiceResolver resolver)
    {
        MyController.resolver = resolver;
    }

    public MyController() 
    { 
        // Now we support a default constructor
        // since maybe someone else is instantiating this type
        // that we don't control.
    }

    protected void DoActions(Type[] types)
    {
        var services = resolver.Resolve(types);

        foreach(var service in services)
        {
            service.DoAction();
        }
    }
}

次に、アプリケーションの起動時にこれを呼び出して、リゾルバーを初期化します。

MyController.InitializeResolver(new NinjectMyServiceResolver(kernal));

これは、依存関係を解決する必要がある XAML で作成された要素を処理するために行いましたが、Service Locator のような要求を削除したかったのです。

構文エラーはご容赦ください:)

私は、興味深いと思われるビュー モデルで Service Locator 呼び出しを使用して MVVM アプリケーションをリファクタリングするというトピックに関する一連のブログ記事を書いています。パート2はもうすぐです:)

http://kellabyte.com/2011/07/24/refactoring-to-improve-maintainability-and-blendability-using-ioc-part-1-view-models/

于 2011-07-27T05:32:53.293 に答える
4

おそらく、Kernel、Types、および MySpecialResolver を廃止し、サブクラスが直接引数として必要な IMyServiceInterface インスタンスを使用して DoActions を呼び出せるようにする必要があります。そして、サブクラスにこれらのインスタンスに到達する方法を決定させます-サブクラスが最もよく知っている必要があります(または、IMyServiceInterface のどのインスタンスが必要かを決定した人物を正確に知らない場合)

于 2011-07-26T17:36:56.150 に答える
0

この回答を投稿する前にもう少し情報が欲しかったのですが、ケリーはその場で私を教えてくれました。:)いわば私の口があるところに私のコードを置くように私に言っています。

Kellyへのコメントで述べたように、リゾルバー/ロケーターを静的実装から注入された実装に移動することに同意しません。派生型に必要な依存関係は、基本クラスに委任するのではなく、そのクラスで解決する必要があるというChrisChrisに同意します。

そうは言っても、これがサービスの場所を削除する方法です...

コマンドインターフェイスの作成

まず、特定の実装用のコマンドインターフェイスを作成します。この場合、DoActionsメソッドで送信される型は属性から生成されるため、を作成しIAttributeCommandます。Matches特定のタイプで使用するコマンドを宣言するために、コマンドにメソッドを追加しています。

public interface IAttributeCommand
{
    bool Matches(Type type);
    void Execute();
}

コマンド実装の追加

インターフェイスを実装するには、コマンドを実行するために必要な特定の依存関係を渡します(コンテナによって解決されます)。Matchesメソッドに述語を追加し、Execute動作を定義します。

public class MyTypeAttributeCommand : IAttributeCommand
{
    MyDependency dependency;
            SomeOtherDependency otherDependency;

    public MyTypeAttributeCommand (MyDependency dependency, ISomeOtherDependency otherDependency)
    {
        this.dependency = dependency;
                    this.otherDependency = otherDependency
    }

    public bool Matches(Type type)
    {
        return type==typeof(MyType)
    }
    public void Execute()
    {
        // do action using dependency/dependencies
    }
}

コマンドをコンテナに登録する

StructureMap(お気に入りのコンテナを使用)では、次のように配列を登録します。

Scan(s=>
       {
                s.AssembliesFromApplicationBaseDirectory();
                s.AddAllTypesOf<IAttributeCommand>();
                s.WithDefaultConventions();
       } 

タイプに基づいてコマンドを選択して実行する

最後に、基本クラスでIAttributeCommand、IOCコンテナーによって注入されるコンストラクター引数に配列を定義します。派生型がtypes配列に渡されると、述語に基づいて正しいコマンドを実行します。

public abstract class MyController : Controller
{
    protected IAttributeCommand[] commands;

    public MyController(IAttributeCommand[] commands) { this.commands = commands); }

    protected void DoActions(Type[] types)
    {
        foreach(var type in types)
        {
            var command = commands.FirstOrDefault(x=>x.Matches(type));
            if (command==null) continue;

            command.Execute();
        }
    }
}

複数のコマンドで1つのタイプを処理できる場合は、実装を変更できます。commands.Where(x=>x.Matches(type)).ToList().ForEach(Execute);

効果は同じですが、クラスの構成方法に微妙な違いがあります。クラスにはIOCコンテナへの結合がなく、サービスの場所もありません。クラスは実際の依存関係を使用して構築できるため、実装はよりテスト可能であり、コンテナー/リゾルバーを接続する必要はありません。

于 2011-07-28T14:23:15.443 に答える