Alex Rivera | Logout

Replace conditional with polymorphism refactoring or similar?

Asked 2010-05-07T22:49:58.830
10

I have tried to ask a variant of this question before. I got some helpful answers, but still nothing that felt quite right to me. It seems to me this shouldn't really be that hard a nut to crack, but I'm not able to find an elegant simple solution. (Here's my previous post, but please try to look at the problem stated here as procedural code first so as not to be influenced by the earlier explanation which seemed to lead to very complicated solutions: Design pattern for cost calculator app? )

Basically, the problem is to create a calculator for hours needed for projects that can contain a number of services. In this case "writing" and "analysis". The hours are calculated differently for the different services: writing is calculated by multiplying a "per product" hour rate with the number of products, and the more products are included in the project, the lower the hour rate is, but the total number of hours is accumulated progressively (i.e. for a medium-sized project you take both the small range pricing and then add the medium range pricing up to the number of actual products). Whereas for analysis it's much simpler, it is just a bulk rate for each size range.

How would you be able to refactor this into an elegant and preferably simple object-oriented version (please note that I would never write it like this in a purely procedural manner, this is just to show the problem in another way succinctly).

I have been thinking in terms of factory, strategy and decorator patterns, but can't get any to work well. (I read Head First Design Patterns a while back, and both the decorator and factory patterns described have some similarities to this problem, but I have trouble seeing them as good solutions as stated there. The decorator example seemed very complicated there for just adding condiments, but maybe it could work better here, I don't know. At

Edit
Report

2 Answers

6

I would tend to start with an enumeration ProjectSize {Small, Medium, Large} and a simple function to return the appropriate enum given a numberOfManuals. From there, I would write different ServiceHourCalculators, the WritingServiceHourCalculator and the AnalysisServiceHourCalculator (because their logic is sufficiently different). Each would take a numberOfManuals, a ProjectSize, and return the number of hours. I'd probably create a map from string to ServiceHourCalculator, so I could say:

ProjectSize projectSize = getProjectSize(_numberOfManuals);
int hours = serviceMap.getService(_serviceType).getHours(projectSize, _numberOfManuals);

This way, when I added a new project size, the compiler would balk at some unhandled cases for each service. It's not all handled in one place, but it is all handled before it will compile again, and that's all I need.

Update I know Java, not C# (very well), so this may not be 100% right, but creating the map would be something like this:

Map<String, ServiceHourCalculator> serviceMap = new HashMap<String, ServiceHourCalculator>();
serviceMap.put("writing", new WritingServiceHourCalculator());
serviceMap.put("analysis", new AnalysisServiceHourCalculator());
answered 2010-05-07T23:33:58.977
1

this is a common problem, there are a few options that i can think of. There are two design pattern that come to mind, firstly the Strategy Pattern and secondly the Factory Pattern. With the strategy pattern it is possible to encapsulate the calculation into an object, for example you could encapsulate your GetHours method into individual classes, each one would represent a calculation based on size. Once we have defined the different calculation strategies we wrap then in a factory. The factory would be responsible for selecting the strategy to perform the calculation just like your if statement in the GetHours method. Any way have a look at the code below and see what you think

At any point you could create a new strategy to perform a different calculation. The strategy can be shared between different objects allowing the same calculation to be used in multiple places. Also the factory could dynamically work out which strategy to use based on configuration, for example

class Program
{
    static void Main(string[] args)
    {
        var factory = new HourCalculationStrategyFactory();
        var strategy = factory.CreateStrategy(1, "writing");

        Console.WriteLine(strategy.Calculate());
    }
}

public class HourCalculationStrategy
{
    public const int Small = 2;
    public const int Medium = 8;

    private readonly string _serviceType;
    private readonly int _numberOfManuals;

    public HourCalculationStrategy(int numberOfManuals, string serviceType)
    {
        _serviceType = serviceType;
        _numberOfManuals = numberOfManuals;
    }

    public int Calculate()
    {
        return this.CalculateImplementation(_numberOfManuals, _serviceType);
    }

    protected virtual int CalculateImplementation(int numberOfManuals, string 
answered 2010-05-08T01:54:48.013

Your Answer