Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to use StructureMap to implement Strategy pattern

My web app has some slight variations in business logic and presentation logic depending on the type of user that is logged in. It seems like getting variations by injecting different concrete classes based on the user type is a good fit for DI. So I'm wondering what features of StructureMap I should use to achieve this (or if I'm way off base on the purposes of DI).

(I just learned that Profiles are not the way to accomplish this because setting the Profile isn't a per-thread operation: Are StructureMap profiles thread safe?)

EDIT

Is this the way to go about this?

public class HomeController
{
    private ISomeDependancy _someDependancy;

    public HomeController(ISomeDependancy someDependancy)
    {
        _someDependancy = someDependancy;
    }

    public string GetNameFromDependancy()
    {
        return _someDependancy.GetName();
    }
}

public interface ISomeDependancy
{
    string GetName();
}

public class VersionASomeDependancy : ISomeDependancy
{
    public string GetName()
    {
        return "My Name is Version A";
    }
}

public class VersionBSomeDependancy : ISomeDependancy
{
    public string GetName()
    {
        return "My Name is Version B";
    }
}

public class VersionARegistry : Registry
{
    public VersionARegistry()
    {
        // build up complex graph here
        ForRequestedType<ISomeDependancy>().TheDefaultIsConcreteType<VersionASomeDependancy>();
    }
}

public class VersionBRegistry : Registry
{
    public VersionBRegistry()
    {
        // build up complex graph here
        ForRequestedType<ISomeDependancy>().TheDefaultIsConcreteType<VersionBSomeDependancy>();
    }
}

public class ContainerA : Container
{
    public ContainerA()
        : base(new VersionARegistry())
    {
    }
}

public class ContainerB : Container
{
    public ContainerB()
        : base(new VersionBRegistry())
    {
    }
}

[TestFixture]
public class Harness
{
    [Test]
    public void ensure_that_versions_load_based_on_named_containers()
    {
        ObjectFactory.Initialize(c =>
        {
            c.ForRequestedType<IContainer>().AddInstances(
                x =>
                {
                    x.OfConcreteType<ContainerA>().WithName("VersionA");
                    x.OfConcreteType<ContainerB>().WithName("VersionB");
                }).CacheBy(InstanceScope.Singleton);
        });

        HomeController controller;

        IContainer containerForVersionA = ObjectFactory.GetNamedInstance<IContainer>("VersionA");
        controller = containerForVersionA.GetInstance<HomeController>();
        Assert.That(controller.GetNameFromDependancy(), Is.EqualTo("My Name is Version A"));

        IContainer containerForVersionB = ObjectFactory.GetNamedInstance<IContainer>("VersionB");
        controller = containerForVersionB.GetInstance<HomeController>();
        Assert.That(controller.GetNameFromDependancy(), Is.EqualTo("My Name is Version B"));
    }
}
like image 841
JeremyWeir Avatar asked Sep 30 '09 17:09

JeremyWeir


People also ask

Can Strategy pattern have multiple methods?

No you can have more than one method on your strategy interface. However, in order for your strategy object to actually use the Strategy pattern, at least one of the method implementations should differ between the different strategies.

When might you use the strategy pattern?

Use the Strategy pattern when you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime.

Does strategy pattern use composition?

The strategy pattern uses composition instead of inheritance. In the strategy pattern, behaviors are defined as separate interfaces and specific classes that implement these interfaces. This allows better decoupling between the behavior and the class that uses the behavior.


2 Answers

One common way to implement this is as Mark described. You have a class that takes in an array of all concrete instances (it must be an array for StructureMap to behave as expected), and then uses some logic to figure out which instance to use.

Some sample code you can paste into a console program or unit test:

var container = new Container(x => x.Scan(scan =>
{
    scan.TheCallingAssembly();
    scan.WithDefaultConventions();
    scan.AddAllTypesOf<IDiscountCalculator>();
}));
var strategy = container.GetInstance<IDiscountStrategy>();
Console.WriteLine(strategy.GetDiscount("Regular", 10)); // 0
Console.WriteLine(strategy.GetDiscount("Normal", 10)); // 1
Console.WriteLine(strategy.GetDiscount("Special", 10)); // 5

which depends on the following types:

public interface IDiscountStrategy 
{
    decimal GetDiscount(string userType, decimal orderTotal);
}

public class DiscountStrategy : IDiscountStrategy
{
    private readonly IDiscountCalculator[] _discountCalculators;

    public DiscountStrategy(IDiscountCalculator[] discountCalculators)
    {
        _discountCalculators = discountCalculators;
    }

    public decimal GetDiscount(string userType, decimal orderTotal)
    {
        var calculator = _discountCalculators.FirstOrDefault(x => x.AppliesTo(userType));
        if (calculator == null) return 0;
        return calculator.CalculateDiscount(orderTotal);
    }
}

public interface IDiscountCalculator
{
    bool AppliesTo(string userType);
    decimal CalculateDiscount(decimal orderTotal);
}

public class NormalUserDiscountCalculator : IDiscountCalculator
{
    public bool AppliesTo(string userType)
    {
        return userType == "Normal";
    }

    public decimal CalculateDiscount(decimal orderTotal)
    {
        return orderTotal * 0.1m;
    }
}

public class SpecialUserDiscountCalculator : IDiscountCalculator
{
    public bool AppliesTo(string userType)
    {
        return userType == "Special";
    }

    public decimal CalculateDiscount(decimal orderTotal)
    {
        return orderTotal * 0.5m;
    }
}
like image 170
Joshua Flanagan Avatar answered Nov 05 '22 19:11

Joshua Flanagan


I would say that this is not the core purpose of DI - that is to wire up and inject dependencies, whatever they might be. No application logic should be involved in the wiring up of components - it should be based stricly on configuration; either by code or .config file. That configuration is application-wide, so it's quite difficult to define a configuration that varies by user.

That said, what you are asking about goes well hand in hand with DI - it's just a bit perpendicular to DI in itself.

For your specific purpose, I would define a new dependency in the form of an interface or abstract base class. This would be a Strategy that selects the correct concrete types (the ones you want to vary) based on the current user.

You can use DI to inject all the available concrete types into this Strategy, which would then have a method or property that returns the correct choice among those injected services, based on the current user.

In all the places where you used to depend on the vary-by-user-services, you remove those old dependencies and replace them with a dependency on the Strategy.

like image 28
Mark Seemann Avatar answered Nov 05 '22 18:11

Mark Seemann