views:

195

answers:

1

I'm trying to implement the Strategy pattern while using Windsor container. Here is what I have:

public class OrderProcessor {
...
    public OrderProcessor(ITaxStrategy strategy) {}

    public void Process(Order order)
    { 
      order.Tax = strategy.CalcTax(order);
    }
}

The problem is, how do I configure my container (other container examples welcome) to have, essentially, criteria for choosing the appropriate dependency. So if I register the following

public class USTaxStrategy : ITaxStrategy { ... }
public class CanadaTaxStrateg : ITaxStrategy { ... }

how do I use the Order.Destination (address) as the criteria for the injected dependency?

+2  A: 

Here are a few options, pick the one you like best. I usually use the first one, it's the simplest.

[TestFixture]
public class TaxStrategyTests {
    [Test]
    public void InjectWithFactory() {
        var container = new WindsorContainer();
        container.AddComponent<USTaxStrategy>();
        container.AddComponent<CanadaTaxStrategy>();
        container.AddComponent<OrderProcessor>();
        container.AddComponent<ITaxStrategyFactory, TaxStrategyFactory>();
        var order = new Order {Country = "US"};
        container.Resolve<OrderProcessor>().Process(order);
        Assert.AreEqual(10, order.Tax);
    }

    [Test]
    public void InjectWithFactoryFromDictionary() {
        var container = new WindsorContainer();
        container.AddFacility<FactorySupportFacility>();
        container.AddComponent<USTaxStrategy>();
        container.AddComponent<CanadaTaxStrategy>();
        container.AddComponent<OrderProcessor>();
        container.Register(Component.For<ITaxStrategyFactory>()
                               .UsingFactoryMethod(kernel => new TaxStrategyFactory2(new Dictionary<string, ITaxStrategy> {
                                   {"US", kernel.Resolve<USTaxStrategy>()},
                                   {"CA", kernel.Resolve<CanadaTaxStrategy>()},
                               })));
        var order = new Order { Country = "US" };
        container.Resolve<OrderProcessor>().Process(order);
        Assert.AreEqual(10, order.Tax);
    }

    [Test]
    public void InjectWithProxy() {
        var container = new WindsorContainer();
        container.AddComponent<USTaxStrategy>();
        container.AddComponent<CanadaTaxStrategy>();
        container.AddComponent<OrderProcessorInterceptor>();
        container.AddComponent<ITaxStrategyFactory, TaxStrategyFactory>();
        container.Register(Component.For<OrderProcessor2>()
                               .LifeStyle.Transient
                               .Interceptors(InterceptorReference.ForType<OrderProcessorInterceptor>()).First);
        var order = new Order {Country = "CA"};
        container.Resolve<OrderProcessor2>().Process(order);
        Assert.AreEqual(5, order.Tax);
    }

    public class OrderProcessorInterceptor : IInterceptor {
        private readonly ITaxStrategyFactory strategyFactory;

        public OrderProcessorInterceptor(ITaxStrategyFactory strategyFactory) {
            this.strategyFactory = strategyFactory;
        }

        public void Intercept(IInvocation invocation) {
            if (invocation.MethodInvocationTarget.Name == "Process") {
                var processor = (OrderProcessor2) invocation.InvocationTarget;
                var order = (Order) invocation.Arguments[0];
                processor.Strategy = strategyFactory.Create(order);
            }
            invocation.Proceed();
        }
    }

    public interface IOrderProcessor {
        void Process(Order order);
    }

    public class OrderProcessor2 : IOrderProcessor {
        public ITaxStrategy Strategy { get; set; }

        public virtual void Process(Order order) {
            order.Tax = Strategy.CalcTax(order);
        }
    }

    public class OrderProcessor : IOrderProcessor {
        private readonly ITaxStrategyFactory strategyFactory;

        public OrderProcessor(ITaxStrategyFactory strategyFactory) {
            this.strategyFactory = strategyFactory;
        }

        public void Process(Order order) {
            var strategy = strategyFactory.Create(order);
            order.Tax = strategy.CalcTax(order);
        }
    }

    public interface ITaxStrategyFactory {
        ITaxStrategy Create(Order o);
    }

    public class TaxStrategyFactory : ITaxStrategyFactory {
        private readonly IKernel kernel;

        public TaxStrategyFactory(IKernel kernel) {
            this.kernel = kernel;
        }

        public ITaxStrategy Create(Order o) {
            if (o.Country == "US")
                return kernel.Resolve<USTaxStrategy>();
            return kernel.Resolve<CanadaTaxStrategy>();
        }
    }

    public class TaxStrategyFactory2: ITaxStrategyFactory {
        private readonly IDictionary<string, ITaxStrategy> strategies;

        public TaxStrategyFactory2(IDictionary<string, ITaxStrategy> strategies) {
            this.strategies = strategies;
        }

        public ITaxStrategy Create(Order o) {
            return strategies[o.Country];
        }
    }

    public interface ITaxStrategy {
        decimal CalcTax(Order order);
    }

    public class USTaxStrategy : ITaxStrategy {
        public decimal CalcTax(Order order) {
            return 10;
        }
    }

    public class CanadaTaxStrategy : ITaxStrategy {
        public decimal CalcTax(Order order) {
            return 5;
        }
    }

    public class Order {
        public string Country { get; set; }
        public decimal Tax { get; set; }
    }
}
Mauricio Scheffer
Simple enough, Thanks Mauricio. I was hoping there was some sort of criteria facility in Windsor that would (err) facilitate the selection and I could have a dependency on the ITaxStrategy without the Factory middleman.
Ryan Cromwell
It's not possible because the container has to know about the particular Order instance, hence the proxy solution. Another option would be moving the Order to some sort of context, but that would complicate things even more.
Mauricio Scheffer
To those who come looking in the future, I put together a post and sample VS 2K8 solution. http://blog.cromwellhaus.com/index.php/2009/10/strategy-pattern-with-castle-windsor/
Ryan Cromwell