Object Oriented Programming - how to avoid duplication in processes that differ slightly depending on a variable
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;
}
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.
Maybe you could have one Processor
per country?
public class FrProcessor : Processor {
protected override string Separator => ".";
protected override string ProcessSpecific(string text) {
return text.Replace("é", "e");
}
}
public class UsaProcessor : Processor {
protected override string Separator => ",";
protected override string ProcessSpecific(string text) {
return text.Capitalise().RemovePunctuation();
}
}
And one base class to handle common parts of the processing:
public abstract class Processor {
protected abstract string Separator { get; }
protected virtual string ProcessSpecific(string text) { }
private string ProcessCommon(string text) {
var split = text.Split(Separator);
return string.Join("|", split);
}
public string Process(string text) {
var s = ProcessSpecific(text);
return ProcessCommon(s);
}
}
Also, you should rework your return types because it won't compile as you wrote them - sometimes a string
method doesn't return anything.
You can create a common interface with a Process
method...
public interface IProcessor
{
string Process(string text);
}
Then you implement it for each country...
public class Processors
{
public class GBR : IProcessor
{
public string Process(string text)
{
return $"{text} (processed with GBR rules)";
}
}
public class FRA : IProcessor
{
public string Process(string text)
{
return $"{text} (processed with FRA rules)";
}
}
}
You can then create a common method for instantiating and executing each country related class...
// also place these in the Processors class above
public static IProcessor CreateProcessor(string country)
{
var typeName = $"{typeof(Processors).FullName}+{country}";
var processor = (IProcessor)Assembly.GetAssembly(typeof(Processors)).CreateInstance(typeName);
return processor;
}
public static string Process(string country, string text)
{
var processor = CreateProcessor(country);
return processor?.Process(text);
}
Then you just need to create and use the processors like so...
// create a processor object for multiple use, if needed...
var processorGbr = Processors.CreateProcessor("GBR");
Console.WriteLine(processorGbr.Process("This is some text."));
// create and use a processor for one-time use
Console.WriteLine(Processors.Process("FRA", "This is some more text."));
Here's a working dotnet fiddle example...
You place all the country-specific processing in each country class. Create a common class (in the Processing class) for all the actual individual methods, so each country processor becomes a list of other common calls, rather than copy the code in each country class.
Note: You'll need to add...
using System.Assembly;
in order for the static method to create an instance of the country class.