Coś, co pojawia się dość często w mojej obecnej pracy, polega na tym, że istnieje uogólniony proces, który musi się wydarzyć, ale potem dziwna część tego procesu musi się nieco różnić w zależności od wartości określonej zmiennej, a ja nie jestem całkiem pewien, jaki jest najbardziej elegancki sposób na poradzenie sobie z tym.
Posłużę się przykładem, który zwykle mamy, który robi rzeczy nieco inaczej w zależności od kraju, z którym mamy do czynienia.
Mam więc klasę, nazwijmy to 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);
}
}
Tyle że tylko niektóre z tych działań muszą się wydarzyć w niektórych krajach. Na przykład tylko 6 krajów wymaga etapu kapitalizacji. Postać do podziału może się zmieniać w zależności od kraju. Wymiana akcentu 'e'
może być wymagana tylko w zależności od kraju.
Oczywiście możesz to rozwiązać, robiąc coś takiego:
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);
}
Ale kiedy masz do czynienia ze wszystkimi możliwymi krajami na świecie, staje się to bardzo uciążliwe. I niezależnie od tego, if
stwierdzenia utrudniają czytanie logiki (przynajmniej, jeśli wyobrażasz sobie bardziej złożoną metodę niż w przykładzie), a złożoność cykliczna zaczyna się dość szybko pełzać.
Więc w tej chwili robię coś takiego:
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);
}
}
Handlery:
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; }
}
Nie jestem też pewien, czy mi się podoba. Logika jest nadal nieco zaciemniona przez cały proces tworzenia fabryki i nie można po prostu spojrzeć na oryginalną metodę i zobaczyć, co się dzieje, na przykład, gdy wykonywany jest proces „GBR”. Ty też skończyć tworzenie wielu klas (w bardziej skomplikowanych przykładów niż to) w stylu GbrPunctuationHandler
, UsaPunctuationHandler
itp ... co oznacza, że trzeba spojrzeć na kilka różnych klas, aby dowiedzieć się wszystkich możliwych działań, które mogą się zdarzyć podczas interpunkcji obsługa. Oczywiście nie chcę jednej wielkiej klasy z miliardem if
oświadczeń, ale równie 20 klas o nieco odmiennej logice również czuje się niezręcznie.
Zasadniczo myślę, że wpadłem w jakiś węzeł OOP i nie wiem, jak go rozwiązać. Zastanawiałem się, czy istnieje jakiś wzorzec, który pomógłby w tego rodzaju procesie?
if (country == "DEU")
ciebie sprawdź if (config.ShouldRemovePunctuation)
.
country
PreProcess
funkcjonalność, która może być zaimplementowana inaczej w zależności od niektórych krajów,DetermineSeparator
może być dostępna dla wszystkich z nich iPostProcess
. Wszystkie z nich mogą miećprotected virtual void
domyślną implementację, a następnie możesz mieć określone dlaProcessors
poszczególnych krajów