У меня есть класс, используемый для обработки платежей клиентов. Все методы этого класса, кроме одного, одинаковы для каждого клиента, за исключением одного, который вычисляет (например), сколько должен пользователь пользователя. Это может сильно варьироваться от клиента к клиенту, и нет простого способа записать логику вычислений в нечто вроде файла свойств, поскольку может быть любое количество пользовательских факторов.
Я мог бы написать некрасивый код, который переключается на основе customerID:
switch(customerID) {
case 101:
.. do calculations for customer 101
case 102:
.. do calculations for customer 102
case 103:
.. do calculations for customer 103
etc
}
но это требует восстановления класса каждый раз, когда мы получаем нового клиента. Какой способ лучше?
[Править] «Дубликат» статьи совершенно другой. Я не спрашиваю, как избежать оператора switch, я спрашиваю о современном дизайне, который лучше всего подходит для этого случая - который я мог бы решить с помощью оператора switch, если бы я хотел написать код динозавра. Приведенные здесь примеры являются общими и бесполезными, поскольку по сути они говорят: «Эй, коммутатор работает довольно хорошо в некоторых случаях, а не в других».
[Править] Я решил использовать самый лучший ответ (создать отдельный класс «Клиент» для каждого клиента, который реализует стандартный интерфейс) по следующим причинам:
Согласованность: я могу создать интерфейс, который гарантирует, что все классы Customer получают и возвращают один и тот же вывод, даже если он создан другим разработчиком
Поддержка: весь код написан на одном и том же языке (Java), поэтому больше нет необходимости изучать отдельный язык кодирования, чтобы поддерживать то, что должно быть очень простой функцией.
Повторное использование: в случае возникновения подобной проблемы в коде я могу повторно использовать класс Customer для хранения любого количества методов для реализации «пользовательской» логики.
Знакомство: я уже знаю, как это сделать, поэтому я могу сделать это быстро и перейти к другим, более актуальным вопросам.
Недостатки:
Каждому новому клиенту требуется компиляция нового класса Customer, что может усложнить процесс компиляции и развертывания изменений.
Каждый новый клиент должен быть добавлен разработчиком - специалист по поддержке не может просто добавить логику в нечто вроде файла свойств. Это не идеально ... но тогда я также не был уверен, как специалист службы поддержки сможет выписать необходимую бизнес-логику, особенно если она сложна со многими исключениями (что вполне вероятно).
Это не будет хорошо масштабироваться, если мы добавим много-много новых клиентов. Этого не ожидается, но если это произойдет, нам придется переосмыслить многие другие части кода, а также этот.
Для тех, кто заинтересован, вы можете использовать Java Reflection для вызова класса по имени:
Payment payment = getPaymentFromSomewhere();
try {
String nameOfCustomClass = propertiesFile.get("customClassName");
Class<?> cpp = Class.forName(nameOfCustomClass);
CustomPaymentProcess pp = (CustomPaymentProcess) cpp.newInstance();
payment = pp.processPayment(payment);
} catch (Exception e) {
//handle the various exceptions
}
doSomethingElseWithThePayment(payment);
Ответы:
Два варианта приходят на ум.
Вариант 1: Сделайте ваш класс абстрактным классом, где метод, который варьируется между клиентами, является абстрактным методом. Затем создайте подкласс для каждого клиента.
Вариант 2. Создайте
Customer
класс илиICustomer
интерфейс, содержащий всю логику, зависящую от клиента. Вместо того, чтобы ваш класс обработки платежей принимал идентификатор клиента, пусть он принимает объектCustomer
илиICustomer
. Всякий раз, когда ему нужно сделать что-то зависящее от клиента, он вызывает соответствующий метод.источник
Возможно, вы захотите написать собственные вычисления как «плагины» для своего приложения. Затем вы будете использовать файл конфигурации, чтобы сообщить программе, какой плагин расчета должен использоваться для какого клиента. Таким образом, ваше основное приложение не нужно будет перекомпилировать для каждого нового клиента - ему нужно только прочитать (или перечитать) файл конфигурации и загрузить новые плагины.
источник
Я бы пошел с набором правил для описания расчетов. Это может быть сохранено в любом постоянном хранилище и изменено динамически.
В качестве альтернативы рассмотрим это:
Где
customerOpsIndex
рассчитывается правильный операционный индекс (вы знаете, какому клиенту нужно какое лечение).источник
Что-то вроде ниже:
Заметьте, у вас все еще есть оператор switch в репо. Хотя в действительности это не обходится, в какой-то момент вам необходимо сопоставить идентификатор клиента с требуемой логикой. Вы можете стать умнее и переместить его в файл конфигурации, поместить отображение в словарь или динамически загрузить логические сборки, но это по существу сводится к переключению.
источник
Похоже, у вас есть соотношение 1: 1 от клиентов к пользовательскому коду, и поскольку вы используете скомпилированный язык, вы должны перестраивать свою систему каждый раз, когда получаете нового клиента
Попробуйте встроенный язык сценариев.
Например, если ваша система работает на Java, вы можете встроить JRuby, а затем для каждого клиента сохранить соответствующий фрагмент кода Ruby. В идеале где-то под контролем версий, либо в том же, либо в отдельном git-репо. А затем оцените этот фрагмент в контексте вашего Java-приложения. JRuby может вызвать любую функцию Java и получить доступ к любому объекту Java.
Это очень распространенная модель. Например, многие компьютерные игры написаны на C ++, но используют встроенные сценарии Lua для определения поведения каждого оппонента в игре.
С другой стороны, если у вас есть многозначное соответствие между клиентами и пользовательским кодом, просто используйте шаблон «Стратегия», как уже предлагалось.
Если отображение не основано на идентификаторе пользователя make, добавьте
match
функцию к каждому объекту стратегии и сделайте упорядоченный выбор используемой стратегии.Вот немного псевдокода
источник
Я собираюсь плыть против течения.
Я бы попробовал реализовать свой собственный язык выражений с помощью ANTLR .
Пока что все ответы основаны на настройке кода. Мне кажется, что реализация конкретных классов для каждого клиента в какой-то момент в будущем будет плохо масштабироваться. Обслуживание будет дорогим и болезненным.
Итак, с Antlr идея состоит в том, чтобы определить свой собственный язык. Вы можете позволить пользователям (или разработчикам) писать бизнес-правила на таком языке.
Принимая ваш комментарий в качестве примера:
С вашим EL, вы сможете сформулировать такие предложения, как:
Потом...
Вы могли бы. Это строка, вы можете сохранить ее как свойство или атрибут.
Я не буду лгать. Это довольно сложно и сложно. Еще сложнее, если бизнес-правила тоже сложны.
Вот некоторые вопросы, которые могут вас заинтересовать:
Примечание: ANTLR генерирует код для Python и Javascript тоже. Это может помочь написать доказательства концепции без лишних затрат.
Если вы находите Antlr слишком сложным, вы можете попробовать с такими библиотеками, как Expr4J, JEval, Parsii. Эти работы с более высоким уровнем абстракции.
источник
Вы можете, по крайней мере, экстернализовать алгоритм, чтобы класс Customer не нуждался в изменении при добавлении нового клиента, используя шаблон проектирования, называемый шаблоном стратегии (он находится в «банде четырех»).
Исходя из приведенного вами фрагмента, можно утверждать, что шаблон стратегии будет менее обслуживаемым или более обслуживаемым, но он по крайней мере устранит знания класса Customer о том, что необходимо сделать (и исключит случай переключения).
Объект StrategyFactory создаст указатель StrategyIntf (или ссылку) на основе CustomerID. Фабрика может вернуть реализацию по умолчанию для клиентов, которые не являются особенными.
Класс Customer должен только спросить у Factory правильную стратегию и затем вызвать ее.
Это очень краткое псевдо-C ++, чтобы показать вам, что я имею в виду.
Недостаток этого решения заключается в том, что для каждого нового клиента, которому требуется особая логика, вам нужно будет создать новую реализацию CalculationsStrategyIntf и обновить фабрику, чтобы вернуть ее соответствующему клиенту (-ам). Это также требует компиляции. Но вы, по крайней мере, избежите постоянно растущего кода спагетти в классе клиентов.
источник
Создайте интерфейс с одним методом и используйте lamdas в каждом классе реализации. Или вы можете анонимный класс для реализации методов для разных клиентов
источник