Search code examples
c#.netoop

Object Oriented Programming - how to avoid duplication in processes that differ slightly depending on a variable


Something that comes up quite a lot in my current work is that there is a generalised process that needs to happen, but then the odd part of that process needs to happen slightly differently depending on the value of a certain variable, and I'm not quite sure what's the most elegant way to handle this.

I'll use the example that we usually have, which is doing things slightly differently depending on the country we're dealing with.

So I have a class, let's call it Processor:

public class Processor
{
    public string Process(string country, string text)
    {
        text.Capitalise();

        text.RemovePunctuation();

        text.Replace("é", "e");

        var split = text.Split(",");

        string.Join("|", split);
    }
}

Except that only some of those actions need to happen for certain countries. For example, only 6 countries require the capitalisation step. The character to split on might change depending on the country. Replacing the accented 'e' might only be required depending on the country.

Obviously you could solve it by doing something like this:

public string Process(string country, string text)
{
    if (country == "USA" || country == "GBR")
    {
        text.Capitalise();
    }

    if (country == "DEU")
    {
        text.RemovePunctuation();
    }

    if (country != "FRA")
    {
        text.Replace("é", "e");
    }

    var separator = DetermineSeparator(country);
    var split = text.Split(separator);

    string.Join("|", split);
}

But when you're dealing with all the possible countries in the world, that becomes very cumbersome. And regardless, the if statements make the logic harder to read (at least, if you imagine a more complex method than the example), and the cyclomatic complexity starts to creep up pretty fast.

So at the moment I'm sort of doing something like this:

public class Processor
{
    CountrySpecificHandlerFactory handlerFactory;

    public Processor(CountrySpecificHandlerFactory handlerFactory)
    {
        this.handlerFactory = handlerFactory;
    }

    public string Process(string country, string text)
    {
        var handlers = this.handlerFactory.CreateHandlers(country);
        handlers.Capitalier.Capitalise(text);

        handlers.PunctuationHandler.RemovePunctuation(text);

        handlers.SpecialCharacterHandler.ReplaceSpecialCharacters(text);

        var separator = handlers.SeparatorHandler.DetermineSeparator();
        var split = text.Split(separator);

        string.Join("|", split);
    }
}

Handlers:

public class CountrySpecificHandlerFactory
{
    private static IDictionary<string, ICapitaliser> capitaliserDictionary
                                    = new Dictionary<string, ICapitaliser>
    {
        { "USA", new Capitaliser() },
        { "GBR", new Capitaliser() },
        { "FRA", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
        { "DEU", new ThingThatDoesNotCapitaliseButImplementsICapitaliser() },
    };

    // Imagine the other dictionaries like this...

    public CreateHandlers(string country)
    {
        return new CountrySpecificHandlers
        {
            Capitaliser = capitaliserDictionary[country],
            PunctuationHanlder = punctuationDictionary[country],
            // etc...
        };
    }
}

public class CountrySpecificHandlers
{
    public ICapitaliser Capitaliser { get; private set; }
    public IPunctuationHanlder PunctuationHanlder { get; private set; }
    public ISpecialCharacterHandler SpecialCharacterHandler { get; private set; }
    public ISeparatorHandler SeparatorHandler { get; private set; }
}

Which equally I'm not really sure I like. The logic is still somewhat obscured by all of the factory creation and you can't simply look at the original method and see what happens when a "GBR" process is executed, for example. You also end up creating a lot of classes (in more complex examples than this) in the style GbrPunctuationHandler, UsaPunctuationHandler, etc... which means that you have to look at several different classes to figure out all of the possible actions that could happen during punctuation handling. Obviously I don't want one giant class with a billion if statements, but equally 20 classes with slightly differing logic also feels clunky.

Basically I think I've got myself into some sort of OOP knot and don't quite know a good way of untangling it. I was wondering if there was a pattern out there that would help with this type of process?


Solution

  • I would suggest encapsulating all options in one class:

    public class ProcessOptions
    {
        public bool Capitalise { get; set; }
        public bool RemovePunctuation { get; set; }
        public bool Replace { get; set; }
        public char ReplaceChar { get; set; }
        public char ReplacementChar { get; set; }
        public bool SplitAndJoin { get; set; }
        public char JoinChar { get; set; }
        public char SplitChar { get; set; }
    }
    

    and pass it into the Process method:

    public string Process(ProcessOptions options, string text)
    {
        if(options.Capitalise)
        {
            text.Capitalise();
        }
    
        if(options.RemovePunctuation)
        {
            text.RemovePunctuation();
        }
    
        if(options.Replace)
        {
            text.Replace(options.ReplaceChar, options.ReplacementChar);
        }
    
        if(options.SplitAndJoin)
        {
            var split = text.Split(options.SplitChar);
            return string.Join(options.JoinChar, split);
        }
    
        return text;
    }