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

3

我希望将策略模式和依赖注入结合起来使用。

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 resolve)获取适当的类而非使用if/else语句?谢谢。

someEnum是一个配置设置还是运行时值? - Yacoub Massad
可能是使用StructureMap实现策略模式的最佳方法的重复问题。 - NightOwl888
4个回答

9
我肯定会使用委托工厂来避免对IoC容器本身的依赖。通过使用键控服务查找,您的代码/工厂将与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()"); } };

你想要进行 switch 的枚举:
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();

将 switch case 放在一个单独的类中:

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();
            }
        }
    }

完整的运行代码示例 - .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();
        }
    }
}

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 ...

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

上下文(将 SetClass 设为 public 以使代码能够执行)...

  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 容器提供。 - Thomas T
如果你注意到工厂正在使用构造函数,那么上下文就不是最初的问题所在了。 - dbugger

3

不需要这样做。

容器不应该包含业务规则。如果这样做,很难理解你获取哪个类以及何时获取它。请阅读最小惊讶原则。

相反,你应该创建一个新的类来决定使用哪种策略。它的契约应该是:

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

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

1
我不同意你不应该这样做。如果你的策略包含由IoC容器管理的依赖关系,那该怎么办呢? - Thomas T
我可以从你的回答中看出来 ;) 关于你的问题:在你的策略选择器中使用容器(服务位置)。这与你的函数没有什么区别,只是一个特定的类获得了该责任,而不是在容器中构建大量基础设施代码。 - jgauffin
这正是我的示例所展示的 :-). 如果 IoC 容器要解析策略中的依赖项,那么策略和工厂必须在 IoC 容器中注册。 - Thomas T
是的,但你把它隐藏在容器设置中。当有人查看想要使用策略的类时,它就变得神奇起来。我的解决方案为您提供了使用容器或更明确地使用它的选项,具体取决于组合策略的复杂程度。 - jgauffin
我认为你错过了这一部分: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(); } } }以及注册:builder.Register<Func<ESomeEnum, IStrategy>>(c => StrategyFactory.GetStrategy); - Thomas T
显示剩余2条评论

1
我使用一个适配器:

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类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接