3

I'd like to join use of Strategy pattern and 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();
}

How to use here DI resolve to get appropriate class instead of if/else? Thanks

Saint
  • 5,397
  • 22
  • 63
  • 107
  • is `someEnum` a configuration setting? or a runtime value? – Yacoub Massad Dec 18 '15 at 16:13
  • Possible duplicate of [Best way to use StructureMap to implement Strategy pattern](http://stackoverflow.com/questions/1499442/best-way-to-use-structuremap-to-implement-strategy-pattern) – NightOwl888 Dec 18 '15 at 16:22

4 Answers4

9

I would definitely use Delegate Factories to avoid having a dependency on the IoC container itself. By using Keyed Service Lookup, your code / factories will be tightly coupled to Autofac.

Here is nice and clean example, without having any dependency on Autofac:

Strategies:

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

The enum you want to switch on:

public enum ESomeEnum
{
    UseStrategyA,
    UseStrategyB,
}

The context which consumes the strategies:

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

And finally the container configuration:

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

If the strategies don't consume any dependencies that are registered in your container, you can new them up yourself, and simplify your configuration like this:

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

And have the switch case in a seperate class:

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

Full running code example - 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();
        }
    }
}
Thomas T
  • 697
  • 1
  • 7
  • 19
4

You can use a Keyed Service Lookup (Autofac Docs) and make a simple factory that resolves the proper type from an enum key.

First configure the Autofac container. Note that the classes based on IBase are keyed to enum values. The factory is registered so that the keyed values are injected into it...

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

The factory looks like this. Note that the IIndex is injected based on the classes set up as keyed to the enum in the config ...

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

Context (made SetClass public so the code would do something)...

  public class Context
    {

        private IBase _usedClass;

        public void SetClass(IBase usedClass)
        {
            _usedClass = usedClass;
        }

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

To see it in action...

 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;
        }
    }
dbugger
  • 15,868
  • 9
  • 31
  • 33
  • You should use constructor injection for your IBase interface in the Context's constructor. By using a setter method you are ignoring the Inversion of Control principle as it is yourself providing the dependency instead of the IoC container. – Thomas T Dec 20 '15 at 14:39
  • If you note the factory is using a constructor. The context is not what the initial question was about. – dbugger Dec 20 '15 at 15:50
3

You don't.

The container should not contain business rules. If it does, there is no easy way to understand which class you get and when. Read about the least astonishment principle.

Instead you should create a new class which purpose is to decide which strategy to use. The contract for it should be something like:

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

Create an implementation for that class and take the interface as a dependency.

jgauffin
  • 99,844
  • 45
  • 235
  • 372
  • 1
    I don't agree that you shouldn't. What if your strategies contain dependencies that are managed by your IoC container? – Thomas T Dec 21 '15 at 09:20
  • I can see that from your answer ;) Regarding your question: Use the container (service location) within your strategy chooser. It's really no difference from your func, except that a specific class get's that responsibility, instead of building a lot of infrastructure code in the container. – jgauffin Dec 21 '15 at 09:31
  • That's exactly what my example shows :-). The strategies and factory has to be registered in the IoC container, if the IoC container is going to resolve dependencies in the strategies. – Thomas T Dec 21 '15 at 11:59
  • yes, but you hide it in the container setup. It becomes magical when someone looks at the classes that want to have a strategy. My solution gives you the option to use the container or to it more explicit, depending on how complex the strategies are to compose. – jgauffin Dec 21 '15 at 12:02
  • I think you missed this part: 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(); } } } and the registration: builder.Register>(c => StrategyFactory.GetStrategy); – Thomas T Dec 21 '15 at 12:21
  • No, I did not. It's not used by the class wanting to get the best strategy for the current use case. It's hidden from the consumer. The one looking at the consumer cannot understand where the strategy choice is made with your example. Readability is important in maintainable code. – jgauffin Dec 21 '15 at 12:24
  • I could have extracted the Func factory to a seperate interface and implementation like You did, but that adds 2 more files to maintain. The implementation will need to have a dependency on the IoC container, so that's why i prefer having the configuration directly in the IoC container, as the container is aware of the business logic at some point anyway. Though i agree on the fact that readability is important, i think it's a matter of how comfortable you are with the use of IoC containers. – Thomas T Dec 21 '15 at 14:32
1

I use an adapter:

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

Now you can inject an IDictionary<MyEnum, IBase> into your constructor:

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

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

I like this because it is easier to write tests around the code that uses these classes since I am just using BCL types.

Don
  • 477
  • 4
  • 5