Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

oop

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?

like image 318
John Darvill Avatar asked Nov 08 '19 10:11

John Darvill


People also ask

How do you avoid code duplication?

Don't Repeat Yourself (DRY): Using DRY or Do not Repeat Yourself principle, you make sure that you stay away from duplicate code as often as you can. Rather you replace the duplicate code with abstractions or use data normalization. To reduce duplicity in a function, one can use loops and trees.

Why do we want to avoid code duplication is it possible to have code with no duplication at all?

The Issue With Code DuplicationDuplication greatly decreases the maintainability of your code. Ideally, introducing a change in business logic should require your team to change one class or function, and no more. Otherwise, a developer has to spend extra effort hunting down all these extra occurrences.

What are the three key factors in Object-Oriented Programming?

There are three major features in object-oriented programming that makes them different than non-OOP languages: encapsulation, inheritance and polymorphism.

Why is object-oriented programming so confusing?

A lot of the confusion when learning OOP comes from trying to pick the right relationship between objects and classes of objects, particularly whether: Object contains Some other Object (or Object1 has an Object2 ) Object is an instance of Class.


2 Answers

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; } 
like image 163
Michał Turczyn Avatar answered Sep 16 '22 18:09

Michał Turczyn


When the .NET framework set out to handle these sorts of problems, it didn't model everything as string. So you have, for instance, the CultureInfo class:

Provides information about a specific culture (called a locale for unmanaged code development). The information includes the names for the culture, the writing system, the calendar used, the sort order of strings, and formatting for dates and numbers.

Now, this class may not contain the specific features that you need, but you can obviously create something analogous. And then you change your Process method:

public string Process(CountryInfo country, string text) 

Your CountryInfo class can then have a bool RequiresCapitalization property, etc, that helps your Process method direct its processing appropriately.

like image 23
Damien_The_Unbeliever Avatar answered Sep 20 '22 18:09

Damien_The_Unbeliever