如何将策略模式与依赖注入一起使用(autofac)

时间:2015-12-18 15:57:53

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

我想加入战略模式和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? 感谢

3 个答案:

答案 0 :(得分:4)

您可以使用密钥服务查找(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是根据在config ...中设置为枚举的类注入的。

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

Context(使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;
        }
    }

答案 1 :(得分:2)

我肯定会使用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 :(得分:0)

我使用适配器:

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<MyEnum, IBase>

class Context()
{
    private IDictionary<MyEnum, IBase> _baseDictionary;
    public Context(IDictionary<MyEnum, IBase> baseDictionary)
    {
        _baseDictionary = baseDictionary;
    }

    public void Do(MyEnum strategy)
    {
        _baseDictionary[strategy].Do();
    }
}

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