2
\$\begingroup\$

It is the case where data contains some field indicating what type of strategy should be picked.

I mean, I often ignore solving the problem like shown below and duplicate the problems that strategy pattern is out to solve in the first place. Is there a pattern for it or is it not viable at all in this case?

Recent example on how I have solved it:

public class MapRentalCostStrategy : IMapper<EquipmentType, IRentalCostStrategy>
{
    private readonly Dictionary<EquipmentType, IRentalCostStrategy> _knownRentalCostStrategy;

    public MapRentalCostStrategy(IMutablePriceConfiguration mutablePriceConfiguration)
    {
        _knownRentalCostStrategy = new Dictionary<EquipmentType, IRentalCostStrategy>
        {
            { EquipmentType.Heavy, new HeavyRentalCostStrategy(mutablePriceConfiguration)},
            { EquipmentType.Specialized, new SpecializedRentalCostStrategy(mutablePriceConfiguration)},
            { EquipmentType.Regular, new RegularRentalCostStrategy(mutablePriceConfiguration)}
        };
    }

    public Func<EquipmentType, IRentalCostStrategy> Create => equipment =>
    {
        if (!_knownRentalCostStrategy.ContainsKey(equipment))
            throw new ArgumentException();

        return _knownRentalCostStrategy[equipment];
    };
}

where EquipmentType is an enum.

Maybe I should inject a service that returns this dictionary? In any case, it seems I am delegating the responsibility (in this case its "object creation", it should not be mappers responsibility to deal with the instantiation of the dictionary).

Maybe I am on the wrong track? Is there a better way?

\$\endgroup\$
  • \$\begingroup\$ I imagine, I could solve this with Chain of Responsibility (dofactory.com/net/chain-of-responsibility-design-pattern#_self1), IoC container (but I don't think it's meant for that) or ... \$\endgroup\$ – Margus May 19 '16 at 8:51
  • \$\begingroup\$ To me your question feels off-topic for this site, and looks more appropriate for Programmers SE (but please follow their tour and read the appropriate Help pages first before posting there). Only remark I've got WRT your code is: don't use ContainsKey, use TryGetValue. \$\endgroup\$ – BCdotWEB May 19 '16 at 9:27
  • \$\begingroup\$ @BCdotWEB Code review spots problems in working code. The code is perfectly fine, it is efficient and does what it is meant to. Typically people do not know that problem exists, I roughly know what the problem is, just I do know how to solve it. I can write an ad hoc solutions, but I'm trying to write more readable code others can follow as well. Programmers SE is more of sharing programming life experience. \$\endgroup\$ – Margus May 19 '16 at 9:43
1
\$\begingroup\$

I think a singleton/enum like pattern would be beneficial. The curious part is the IMutablePriceConfiguration object seems to vary based on the situation.

Remove the IMutablePriceConfiguration as a dependency for the cost strategy objects.

Next:

public sealed class EquipmentType
{
    public static readonly Heavy = new EquipmentType(1, "Heavy", new HeavyRentalCostStrategy());
    public static readonly Specialized = new EquipmentType(2, "Specialized", new SpecializedRentalCostStrategy());
    public static readonly Regular = new EquipmentType(3, "Regular", new RegularRentalCostStrategy());

    public static readonly IEnumerable<EquipmentType> All = new EquipmentType[]
    {
        Heavy,
        Specialized,
        Regular
    };

    public static EquipmentType Find(int id)
    {
        return All.SingleOrDefault(e => e.Id == id);
    }

    public static EquipmentType Find(string name)
    {
        return All.SingleOrDefault(e => e.Name == name);
    }

    private IRentalCostStrategy CostStrategy { get; set; }
    public string Name { get; private set; }
    public int Id { get; private set; }

    private EquipmentType(int id, string name, IRentalCostStrategy costStrategy)
    {
        Id = id;
        Name = name;
        CostStrategy = costStrategy;
    }

    public double CalculateRentalCost(IMutablePriceConfiguration mutablePriceConfiguration)
    {
        return CostStrategy.CalculateRentalCost(mutablePriceConfiguration);
    }
}
  • The IMutablePriceConfiguration becomes an argument to the CalculateRenderCost method on the EquipmentType class

  • The EquipmentType class has a private constructor so you are guaranteed to only have those three instances available. No one can incorrectly (or maliciously) create equipment types and their cost strategies.

  • You can access each equipment type in an enum-like syntax:

    EquipmentType.Regular
    
  • You can find by id or name:

    EquipmentType.Find(1);
    EquipmentType.Find("Specialized");
    
  • You can blindly loop over all available equipment types:

    foreach (var equipmentType in EquipmentType.All)
    {
        // ...
    }
    
  • As long as you have an IMutablePriceConfiguration object, you can blindly call the CalculateRentalCost method and get a result back. No need for ifs or switchs peppered all over your code.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.