По мере продвижения по карьерной лестнице я сталкивался со все большим количеством проектов, в которых «дизайн класса достаточно хорош, если он работает». Из-за этогопрограммисты часто придумывают недальновидные проекты классов, которые могут создать кошмары для обслуживания.

Ради этого поста я приведу простой пример, хотя и из реальной жизни. Рассмотрим приложение, целью которого является обработка платежей. Основным доменным классом в таком приложении является класс Money. Ведь обработка платежей невозможна без Money.

Часто встречающаяся версия класса Money выглядит так:

Этот класс раскрывает свое внутреннее состояние.

Когда вам нужно делать бизнес-операции с этим классом, проблема проявляется. Бизнес-логика с классами в стиле объектов данных, такими как выше, как правило, обрабатывается с помощью определенных классов Service/Transaction-Script.

Хотя это прекрасно подходит для очень маленьких приложений, это недопустимо для более крупных приложений со сложной бизнес-логикой или тех, которые часто обновляются и добавляют все больше и больше функций.

Например, инцидент, который я видел, касался

  1. необходимость знать, был ли окончательный счет для клиента выше определенного порогового значения в 275 евро, и
  2. сделать что-то очень конкретное, если это так.

Обратите внимание, что это приложение было доступно в различных европейских странах, даже в тех, которые неперешли на евро.

Класс, в котором это было сделано, выглядел примерно так:

В конкретном случае, о котором я говорю, этот Service, который вы видите выше, был реализован в двух отдельных модулях. Без ведома разработчика, внедрившего эту новую функцию, сумма thresholdValue в параметре функции была отправлена ​​в евро в одном модуле, где это была местная рыночная валюта в другом модуле. .

Ни код, ни семантика не сделали это явным. Это была катастрофа, которая ждала своего часа.

Сделать это немного лучше

Теперь мы можем пойти по более простому пути и просто переименовать параметр функции в thresholdValueInEuros, чтобы обозначить намерение.

Дальнейшие изменения

Мы можем настроить его немного больше. В этой итерации мы можем сделать параметр функции экземпляром самого класса Money. Итак, теперь определение функции выглядит так

Можем ли мы сделать больше?

Абсолютно.

Моя проблема с приведенным выше решением заключается в том, что основная логика, связанная с Money, теперь будет распределена между двумя другими классами: ConversionService (для преобразования) и BillCalculatingService (одна Money имеет большее числовое значение, чем другая).

Это означает, что все эти три класса связаны.

Мы взяли здесь простой пример одного класса, но представьте себе сценарий, в котором все приложение написано с использованием объектов данных, таких как класс Money, и вся бизнес-логика распространяется в различных классах Service. По мере того, как проекты разрастаются и добавляют все больше и больше функций, появляется больше шансов ввести чрезвычайно связанные классы, что делает изменения очень громоздкими.

Мой предпочтительный подход

Суть моего предпочтительного подхода заключается в создании инкапсулированных классов, которые не раскрывают свое внутреннее состояние.

Другая реализация класса Money будет выглядеть так

Здесь есть два отличия

  • и значение, и валюта не передаются через геттеры
  • сравнение значений выполняется с помощью самого классаMoney.

Это устраняет необходимость сравнения Money в классе BillCalculatoryService , а логика преобразования в ConversionService вызываетсячерез Money тоже класс.

Но я бы пошел дальше. Вместо простого вызова метода ConversionService я бы переместил эту логику в другое место.

При таком подходе каждый раз, когда в приложение вводится новая валюта (например, при развертывании в новой стране), необходимость указывать коэффициент конвертации становится обязательной, так как теперь это будет проверка во время компиляции.

Думая еще больше в «Объектах»

Последнее изменение, о котором я подумал, — преобразовать параметр thresholdAmount в конкретный объект ThresholdAmount.

Теперь, если вы думаете об этом, ThresholdAmountis-a Money, но единственная разница в том, как он инициализируется, первое делается через конфигурация. (Помните, это значение можно было настроить)

Вот почему я думаю, что может существовать объект ThresholdAmount.

Вывод

Слишком продвинутый дизайн может иметь неприятные последствия.

Слишком мало может тоже!

Нам нужно научиться находить золотую середину, и часто это происходит с опытом.

Ключ в том, чтобы знать, когда необходим рефакторинг, а затем убедить в этом свою команду и, возможно, своего менеджера. Хорошо спроектированные классы часто являются серьезным инструментом, помогающим вам быстрее внедрять новые функции или избегать ошибок. Следующий человек, который поддерживает вашу кодовую базу, будет вам благодарен.