Что-то, что встречается довольно часто в моей текущей работе, заключается в том, что существует обобщенный процесс, который должен произойти, но тогда нечетная часть этого процесса должна происходить немного по-другому в зависимости от значения определенной переменной, и я не Я уверен, что это самый элегантный способ справиться с этим.
Я буду использовать пример, который у нас обычно есть, который делает вещи немного по-другому в зависимости от страны, с которой мы имеем дело.
Итак, у меня есть класс, давайте назовем его 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);
}
}
За исключением того, что только некоторые из этих действий должны произойти для определенных стран. Например, только 6 стран требуют шага капитализации. Персонаж для разделения может меняться в зависимости от страны. Замена акцентированного 'e'
может потребоваться только в зависимости от страны.
Очевидно, вы можете решить это, выполнив что-то вроде этого:
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);
}
Но когда вы имеете дело со всеми возможными странами мира, это становится очень громоздким. И, тем if
не менее, операторы затрудняют чтение логики (по крайней мере, если вы представите более сложный метод, чем в примере), и цикломатическая сложность начинает довольно быстро нарастать.
Так что на данный момент я делаю что-то вроде этого:
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);
}
}
Обработчики:
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; }
}
Что в равной степени я не уверен, что мне нравится. Логика все еще несколько скрыта при создании фабрики, и вы не можете просто посмотреть на оригинальный метод и посмотреть, что происходит, например, при выполнении процесса «GBR». Вы также в конечном итоге создаете много классов (в более сложных примерах, чем этот) в стиле GbrPunctuationHandler
и UsaPunctuationHandler
т. Д., Что означает, что вам нужно взглянуть на несколько разных классов, чтобы выяснить все возможные действия, которые могут произойти во время пунктуации обработки. Очевидно, я не хочу одного гигантского класса с миллиардом if
утверждений, но в равной степени 20 классов со слегка отличающейся логикой также чувствуют себя неуклюже.
По сути, я думаю, что завелся в какой-то ООП-узел и не знаю, как правильно его распутать. Мне было интересно, есть ли шаблон, который поможет с этим типом процесса?
PreProcess
функциональность, которая может быть реализована по-разному в зависимости от некоторых стран,DetermineSeparator
может быть доступна для всех из них, и aPostProcess
. Все они могут бытьprotected virtual void
с реализацией по умолчанию, а затем вы можете иметь конкретную дляProcessors
каждой страныif (country == "DEU")
вас проверитьif (config.ShouldRemovePunctuation)
.country
Ответы:
Я бы предложил инкапсулировать все параметры в одном классе:
и передать его в
Process
метод:источник
CountrySpecificHandlerFactory
... o_0public class ProcessOptions
действительно должно быть просто[Flags] enum class ProcessOptions : int { ... }
...ProcessOptions
. Очень удобно.Когда платформа .NET намеревалась решать подобные проблемы, она не смогла все смоделировать
string
. Итак, у вас есть, например,CultureInfo
класс :Теперь этот класс может не содержать специфических функций, которые вам нужны, но вы, очевидно, можете создать нечто аналогичное. И тогда вы измените свой
Process
метод:В этом случае ваш
CountryInfo
класс может иметьbool RequiresCapitalization
свойство и т. Д., Которые помогут вашемуProcess
методу соответствующим образом направить его обработку.источник
Может быть, вы могли бы иметь один
Processor
на страну?И один базовый класс для обработки общих частей обработки:
Кроме того, вы должны переработать возвращаемые типы, потому что они не будут компилироваться так, как вы их написали - иногда
string
метод ничего не возвращает.источник
Вы можете создать общий интерфейс с
Process
методом ...Тогда вы реализуете это для каждой страны ...
Затем вы можете создать общий метод для создания и выполнения каждого класса, связанного со страной ...
Тогда вам просто нужно создавать и использовать процессоры, как так ...
Вот рабочий пример скрипта dotnet ...
Вы размещаете все специфичные для страны обработки в каждом классе страны. Создайте общий класс (в классе Processing) для всех реальных отдельных методов, чтобы каждый процессор страны становился списком других общих вызовов, а не копировал код в каждом классе страны.
Примечание: вам нужно будет добавить ...
чтобы статический метод создал экземпляр класса страны.
источник
Process
и вместо этого использовать ее один раз, чтобы получить правильный IP-процессор? Обычно вы обрабатываете много текста в соответствии с правилами той же страны.Process("GBR", "text");
он выполняет статический метод, который создает экземпляр процессора GBR и выполняет метод Process для этого. Он выполняет его только в одном экземпляре для данного типа страны.Несколько версий назад, в C # swtich была полная поддержка сопоставления с образцом . Так что дело «совпадение нескольких стран» легко сделать. Хотя у него все еще нет способности к провалу, один вход может сопоставить несколько случаев с сопоставлением с образцом. Это может сделать этот спам немного понятнее.
Npw переключатель обычно можно заменить на коллекцию. Вы должны использовать делегатов и словарь. Процесс можно заменить на.
Тогда вы могли бы сделать словарь:
Я использовал functionNames, чтобы передать делегат. Но вы можете использовать лямбда-синтаксис для предоставления всего кода там. Таким образом, вы можете просто спрятать всю коллекцию, как любую другую большую коллекцию. И код становится простым поиском:
Это почти два варианта. Возможно, вы захотите использовать перечисления вместо строк для сопоставления, но это незначительная деталь.
источник
Возможно, я бы (в зависимости от деталей вашего варианта использования) выбрал
Country
бы «реальный» объект вместо строки. Ключевое слово "полиморфизм".Так что в основном это будет выглядеть так:
Тогда вы можете создавать специализированные страны для тех, кто вам нужен. Примечание: вам не нужно создавать
Country
объекты для всех стран, вы можете иметьLatinlikeCountry
, или дажеGenericCountry
. Там вы можете собрать то, что должно быть сделано, даже повторно используя другие, например:Или похожие.
Country
может быть на самом делеLanguage
, я не уверен насчет варианта использования, но я вас понимаю.Кроме того, метод, конечно, не
Process()
должен быть тем, что вам действительно нужно сделать. КакWords()
или как угодно.источник
Вы хотите поручить (кивнуть цепочке ответственности) что-то, что знает о его собственной культуре. Поэтому используйте или создайте конструкцию типа Country или CultureInfo, как указано выше в других ответах.
Но в целом и в основном ваша проблема заключается в том, что вы берете процедурные конструкции, такие как «процессор», и применяете их к ОО. ОО представляет собой представление концепций реального мира из сферы бизнеса или проблем в программном обеспечении. Процессор не переводит ничего в реальный мир, кроме самого программного обеспечения. Всякий раз, когда у вас есть классы, такие как Процессор, Менеджер или Губернатор, должны прозвучать сигналы тревоги.
источник
Цепочка ответственности - это то, что вы можете искать, но в ООП это несколько обременительно ...
Как насчет более функционального подхода с C #?
ПРИМЕЧАНИЕ. Конечно, не обязательно все должно быть статичным. Если класс Process нуждается в состоянии, вы можете использовать экземплярный класс или частично примененную функцию;).
Вы можете построить Процесс для каждой страны при запуске, сохранить каждую в индексированной коллекции и извлекать их при необходимости за O (1).
источник
Я бы просто реализовать подпрограммы
Capitalise
, иRemovePunctuation
т.д. , как подпроцессы , которые могут быть с обменивались сообщениямиtext
иcountry
параметрами , и возвращал бы обработанный текст.Используйте словари, чтобы сгруппировать страны, которые соответствуют определенному атрибуту (если вы предпочитаете списки, это будет работать также с незначительными затратами на производительность). Например:
CapitalisationApplicableCountries
иPunctuationRemovalApplicableCountries
.источник
Я чувствую, что информация о странах должна храниться в данных, а не в коде. Таким образом, вместо класса CountryInfo или словаря CapitalisationApplicableCountries у вас может быть база данных с записью для каждой страны и поле для каждого шага обработки, а затем обработка может проходить через поля для данной страны и обрабатываться соответствующим образом. В этом случае обслуживание ведется главным образом в базе данных, а новый код необходим только тогда, когда необходимы новые шаги, а данные могут быть удобочитаемыми в базе данных. Это предполагает, что шаги независимы и не мешают друг другу; если это не так, все сложно.
источник