Я читал, что конструкторы не должны содержать бизнес-логику, но я также читал, что конструкторы могут делать то, что им нужно, для инициализации состояния объекта.
Оба эти утверждения - хороший совет, и они не противоречат.
Я думаю, что ваш вопрос действительно о том, что считается "бизнес-логикой". Общая идея заключается в том, что «бизнес-логика» реализует «бизнес-правила», то есть поведения, о которых просил или мог бы заботиться фактический «бизнес-клиент» программного обеспечения (не разработчик).
Например, предположим, что бизнес-правило гласит, что в именах пользователей допускаются буквы кириллицы, , если либо (1) имя пользователя также содержит буквы латинского алфавита, либо (2) каждая буква кириллицы в имени пользователя является легко принять за латинский аналог. (Это бизнес-правило помогает предотвратить спуфинг: я не могу выдать себя за учетную запись с именем «Ben», создав одну учетную запись с именем «Веn», и учетную запись с именем «Дима» путем создания учетной записи с именем «Димa».)
Теперь у вас, по-видимому, есть класс объектов бизнес-домена, называемый чем-то вроде User
, который представляет пользователя приложения; таким образом, у вас может возникнуть желание реализовать это бизнес-правило как инвариант класса User
, что означает, что в буквальном смысле невозможно иметь экземпляр User
, который нарушает это бизнес-правило. Для этого вы реализуете это правило в конструкторе (возможно, делегируя какой-то метод validateUsername
или что-то в этом роде).
Одна проблема с этим состоит в том, что буквально невозможно иметь пользователя с именем пользователя, которое нарушает это правило; если важный клиент звонит, чтобы потребовать имя пользователя сор , то кому-то может понадобиться войти в базу данных и вручную создать этого пользователя - в этом случае вы не хотите, чтобы класс User
взорвал при первой попытке загрузить этого пользователя из базы данных.
Другая проблема заключается в том, что, поскольку правило со временем усложняется, вы можете в конечном итоге извлечь соответствующие данные в базу данных или хранилище конфигурации, и вам, очевидно, не нужно подключение к базе данных только для создания экземпляра User
в юнит-тесте.
Таким образом, лучшим подходом является различие между техническими ограничениями для User
класса - например, поля, которые должны быть заполнены или что-то еще взорвется, поля, которые не должны быть видоизменены, или что-то другое взорвется - и бизнес-ограничения для учетных записей пользователей . Сам класс должен строго придерживаться первого, но не второго. Вместо этого у вас должен быть отдельный метод для проверки пользователей, и вызывайте этот метод перед добавлением пользователя в базу данных.
К сожалению, я не понимаю ваш класс MapManager
достаточно хорошо, чтобы комментировать его (и я на самом деле немного скептически отношусь к тому, что он имеет смысл как связный класс), но если это так помогает, вот несколько примеров логики инициализации, которые не считаются "бизнес-логикой":
- Если класс представляет структуру данных, такую как двусвязный список (
java.util.LinkedList
), хеш-таблица (java.util.HashMap
), красно-черное дерево (java.util.TreeMap
или java.util.TreeSet
), то это абсолютно Ожидается, что его конструкторы будут включать логику для инициализации структуры данных - особенно если у него есть конструктор, который берет существующую коллекцию и копирует ее содержимое (как это делают все примеры). Это не «бизнес-логика», потому что у бизнес-клиентов нет мнения о них; они, вероятно, даже не знают терминов «связанный список» и «дерево». Все в этих классах относится к категории "технические", а не "деловые".
- Если класснеизменяемое поле, которое не должно быть нулевым, тогда абсолютно ожидаемо, что его конструктор проверит это и выдаст
java.lang.NullPointerException
с информативным сообщением. Здесь может быть какое-то влияние бизнес-правила на техническую реализацию - мы можем представить новое бизнес-требование, которое довольно прямо переводится как «это поле должно быть обнуляемым сейчас» (хотя на самом деле это может быть реализовано другим способом, например, изменив поле на java.util.Optional
) - но это техническое правило, потому что есть вероятность, что в нисходящем коде он будет использоваться, например писать такие вещи, как this.username.toLowerCase()
без проверки на ноль. (Это вопрос прагматизма; зачем писать дополнительный код для поддержки пустых имен пользователей, если они вряд ли когда-либо будут разрешены? Лучше просто проверить заранее.)