【问题标题】:How to use strategy pattern together with dependency injection (autofac)如何将策略模式与依赖注入(autofac)一起使用
【发布时间】:2015-12-18 15:57:52
【问题描述】:

我想加入策略模式和 DI 的使用。

class A : IBase
{
    public void Do();
}

class B : IBase
{
    public void Do();
}

interface IBase
{
    void Do();
}

class Context()
{
    private _usedClass;
    void SetClass(IBase usedClass)
    {
        _usedClass = usedClass;
    }

    public void Do()
    {
        _usedClass.Do();
    }
}

void Main()
{
    var context = new Context();
    var someEnum = SomeMethod();

    //how to use here DI resolve to get appropriate class instead of if/else?
    if (someEnum == MyEnum.A)
        context.SetClass(new A());
    else if (someEnum == MyEnum.B)
        context.SetClass(new B());

    context.Do();
}

如何在这里使用 DI 解析来获得适当的类而不是 if/else? 谢谢

【问题讨论】:

标签: .net design-patterns dependency-injection autofac strategy-pattern


【解决方案1】:

我肯定会使用Delegate Factories 来避免对 IoC 容器本身的依赖。通过使用 Keyed Service Lookup,您的代码/工厂将与 Autofac 紧密耦合。

这是一个不错且干净的示例,不依赖于 Autofac:

策略:

    public interface IStrategy { void Do(); }
    public class ConcreteStrategyA : IStrategy { public void Do() { Console.WriteLine("Called ConcreteStrategyA.Do()"); } };
    public class ConcreteStrategyB : IStrategy { public void Do() { Console.WriteLine("Called ConcreteStrategyB.Do()"); } };

你要开启的枚举:

public enum ESomeEnum
{
    UseStrategyA,
    UseStrategyB,
}

使用策略的上下文:

private readonly Func<ESomeEnum, IStrategy> _strategyFactory;

public Context(Func<ESomeEnum, IStrategy> strategyFactory)
{
    _strategyFactory = strategyFactory;
}

public void DoSomething()
{
    _strategyFactory(ESomeEnum.UseStrategyB).Do();
    _strategyFactory(ESomeEnum.UseStrategyA).Do();
}

最后是容器配置:

    var builder = new ContainerBuilder();

    builder.RegisterType<Context>().AsSelf().SingleInstance();

    builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(IStrategy)))
           .Where(t => typeof(IStrategy).IsAssignableFrom(t))
           .AsSelf();

    builder.Register<Func<ESomeEnum, IStrategy>>(c =>
    {
        var cc = c.Resolve<IComponentContext>();
        return (someEnum) =>
        {
            switch (someEnum)
            {
                case ESomeEnum.UseStrategyA:
                    return cc.Resolve<ConcreteStrategyA>();
                case ESomeEnum.UseStrategyB:
                    return cc.Resolve<ConcreteStrategyB>();
                default:
                    throw new ArgumentException();
            }
        };
    });

    var container = builder.Build();

    container.Resolve<Context>().DoSomething();

如果策略不消耗容器中注册的任何依赖项,您可以自己新建它们,并像这样简化配置:

var builder = new ContainerBuilder();

builder.RegisterType<Context>().AsSelf().SingleInstance();
builder.Register<Func<ESomeEnum, IStrategy>>(c => StrategyFactory.GetStrategy);

var container = builder.Build();

container.Resolve<Context>().DoSomething();

并将开关盒放在一个单独的类中:

public static class StrategyFactory
    {
        internal static IStrategy GetStrategy(ESomeEnum someEnum)
        {
            switch (someEnum)
            {
                case ESomeEnum.UseStrategyA:
                    return new ConcreteStrategyA();
                case ESomeEnum.UseStrategyB:
                    return new ConcreteStrategyB();
                default:
                    throw new ArgumentException();
            }
        }
    }

完整运行代码示例 - check in .NET Fiddle:

using Autofac;
using System;
using System.Reflection;

namespace Samples.Autofac.StrategyPattern
{
    public interface IStrategy { void Do(); }

    public class ConcreteStrategyA : IStrategy { public void Do() { Console.WriteLine("Called ConcreteStrategyA.Do()"); } };
    public class ConcreteStrategyB : IStrategy { public void Do() { Console.WriteLine("Called ConcreteStrategyB.Do()"); } };

    public enum ESomeEnum
    {
        UseStrategyA, UseStrategyB,
    }

    public class Context
    {
        private readonly Func<ESomeEnum, IStrategy> _strategyFactory;

        public Context(Func<ESomeEnum, IStrategy> strategyFactory)
        {
            _strategyFactory = strategyFactory;
        }

        public void DoSomething()
        {
            _strategyFactory(ESomeEnum.UseStrategyB).Do();
            _strategyFactory(ESomeEnum.UseStrategyA).Do();
        }
    }

    public class AutofacExample
    {
        public static void Main()
        {
            var builder = new ContainerBuilder();

            builder.RegisterType<Context>().AsSelf().SingleInstance();
            builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(IStrategy)))
                   .Where(t => typeof(IStrategy).IsAssignableFrom(t))
                   .AsSelf();
            builder.Register<Func<ESomeEnum, IStrategy>>(c =>
            {
                var cc = c.Resolve<IComponentContext>();
                return (someEnum) =>
                {
                    switch (someEnum)
                    {
                        case ESomeEnum.UseStrategyA:
                            return cc.Resolve<ConcreteStrategyA>();
                        case ESomeEnum.UseStrategyB:
                            return cc.Resolve<ConcreteStrategyB>();
                        default:
                            throw new ArgumentException();
                    }
                };
            });

            var container = builder.Build();

            container.Resolve<Context>().DoSomething();
        }
    }
}

【讨论】:

    【解决方案2】:

    您可以使用键控服务查找 (Autofac Docs) 并创建一个简单的工厂,从枚举键中解析正确的类型。

    首先配置 Autofac 容器。请注意,基于 IBase 的类被键入到枚举值。工厂已注册,以便将键值注入其中...

     public class AutofacConfig
        {
            private static IContainer _container;
    
            public static IContainer Container
            {
                get { return _container; }
            }
    
            public static void IoCConfiguration()
            {
                var builder = new ContainerBuilder();
                builder.RegisterType<A>().Keyed<IBase>(MyEnum.A);
                builder.RegisterType<B>().Keyed<IBase>(MyEnum.B);
                builder.RegisterType<SomeFactory>();
                _container = builder.Build();
            }
        }
    

    工厂是这样的。请注意,IIndex 是根据在配置中设置为键入枚举的类注入的...

     class SomeFactory
        {
            public IIndex<MyEnum, IBase> Classes { get; private set; }
            public SomeFactory(IIndex<MyEnum, IBase> classes)
            {
                Classes = classes;
            }
        }
    

    上下文(将 SetClass 公开,以便代码执行某些操作)...

      public class Context
        {
    
            private IBase _usedClass;
    
            public void SetClass(IBase usedClass)
            {
                _usedClass = usedClass;
            }
    
            public void Do()
            {
                _usedClass.Do();
            }
        }
    

    查看它的实际效果...

     class Program
        {
            static void Main(string[] args)
            {
                AutofacConfig.IoCConfiguration();
                using (var scope = AutofacConfig.Container.BeginLifetimeScope())
                {
                    var factory = scope.Resolve<SomeFactory>();
                    var someEnum = GetEnum();
                    var someClass = factory.Classes[someEnum];
                    var context = new Context();
                    context.SetClass(someClass);
                    context.Do();
                }
            }
    
            private static MyEnum GetEnum()
            {
                if (DateTime.Now.Millisecond%2 == 0)
                {
                    return MyEnum.A;
                }
                return MyEnum.B;
            }
        }
    

    【讨论】:

    • 您应该在 Context 的构造函数中为您的 IBase 接口使用构造函数注入。通过使用 setter 方法,您忽略了控制反转原则,因为它是您自己提供依赖项而不是 IoC 容器。
    • 如果您注意到工厂正在使用构造函数。上下文不是最初的问题。
    【解决方案3】:

    你没有。

    容器不应包含业务规则。如果是这样,就没有简单的方法来了解您上哪门课以及何时上学。了解最小惊讶原则。

    相反,您应该创建一个新类,其目的是决定使用哪种策略。它的合同应该是这样的:

    public interface IMyStrategyChooser
    {
        IBase GetBestStrategyFor(YourEnum enum);
    }
    

    为该类创建一个实现并将接口作为依赖项。

    【讨论】:

    • 我不同意你不应该这样做。如果您的策略包含由 IoC 容器管理的依赖项怎么办?
    • 我可以从您的回答中看出这一点;)关于您的问题:在您的策略选择器中使用容器(服务位置)。它和你的 func 真的没有什么区别,只是一个特定的类承担了这个责任,而不是在容器中构建大量的基础设施代码。
    • 这正是我的示例所显示的 :-)。如果 IoC 容器要解决策略中的依赖关系,则必须在 IoC 容器中注册策略和工厂。
    • 是的,但是您将其隐藏在容器设置中。当有人查看想要制定策略的课程时,它变得很神奇。我的解决方案为您提供了使用容器或更明确的选项,具体取决于组合策略的复杂程度。
    • 我想你错过了这部分: public static class StrategyFactory { internal static IStrategy GetStrategy(ESomeEnum someEnum) { switch (someEnum) { case ESomeEnum.UseStrategyA: return new ConcreteStrategyA(); case ESomeEnum.UseStrategyB: return new ConcreteStrategyB();默认值:抛出新的 ArgumentException(); } } } 和注册:builder.Register>(c => StrategyFactory.GetStrategy);
    【解决方案4】:

    我使用适配器:

    builder.RegisterType<A>().Keyed<IBase>(MyEnum.A);
    builder.RegisterType<B>().Keyed<IBase>(MyEnum.B);
    
    builder.RegisterAdapter<IIndex<MyEnum, IBase>,
                    IDictionary<MyEnum, IBase>>(idx =>
                {
                    var d = new Dictionary<MyEnum, IBase>();
                    foreach (MyEnum e in Enum.GetValues(typeof(MyEnum)))
                    {
                        d[e] = idx[e];
                    }
    
                    return d;
                });
    

    现在您可以将IDictionary&lt;MyEnum, IBase&gt; 注入到您的构造函数中:

    class Context()
    {
        private IDictionary<MyEnum, IBase> _baseDictionary;
        public Context(IDictionary<MyEnum, IBase> baseDictionary)
        {
            _baseDictionary = baseDictionary;
        }
    
        public void Do(MyEnum strategy)
        {
            _baseDictionary[strategy].Do();
        }
    }
    

    我喜欢这样,因为我只使用 BCL 类型,因此更容易围绕使用这些类的代码编写测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-07
      • 2018-09-25
      • 2010-12-14
      • 1970-01-01
      • 2018-02-22
      • 1970-01-01
      相关资源
      最近更新 更多