Это более правильная запись моего первоначального комментария под вашим вопросом. Ответы на вопросы, заданные ФП, можно найти внизу этого ответа. Также, пожалуйста, проверьте важную заметку, расположенную в том же месте.
То, что вы сейчас описываете, Sipo, - это шаблон проектирования, называемый Active record . Как и во всем, даже этот нашел свое место среди программистов, но был отброшен в пользу репозитория и шаблонов отображения данных по одной простой причине - масштабируемости.
Короче говоря, активная запись - это объект, который:
- представляет объект в вашем домене (включает бизнес-правила, знает, как обрабатывать определенные операции над объектом, например, если вы можете или не можете изменить имя пользователя и т. д.),
- знает, как извлечь, обновить, сохранить и удалить объект.
Вы решаете несколько проблем с вашим текущим дизайном, и основная проблема вашего дизайна решается в последнем, шестом, пункте (последний, но не менее важный, я думаю). Когда у вас есть класс, для которого вы разрабатываете конструктор, и вы даже не знаете, что должен делать конструктор, класс, вероятно, делает что-то не так. Это случилось в вашем случае.
Но исправить дизайн на самом деле довольно просто, разбив представление сущностей и логику CRUD на два (или более) класса.
Вот как выглядит ваш дизайн сейчас:
Employee
- содержит информацию о структуре сотрудника (его атрибутах) и методах изменения сущности (если вы решите пойти по пути изменчивости), содержит логику CRUD для Employee
сущности, может возвращать список Employee
объектов, принимает Employee
объект, когда вы хотите обновить сотрудника, может вернуть один с Employee
помощью метода, какgetSingleById(id : string) : Employee
Вау, класс кажется огромным.
Это будет предлагаемое решение:
Employee
- содержит информацию о структуре сотрудников (ее атрибутах) и методах изменения сущности (если вы решите пойти по пути изменчивости)
EmployeeRepository
- содержит логику CRUD для Employee
объекта, может возвращать список Employee
объектов, принимает Employee
объект, когда вы хотите обновить сотрудника, может возвращать единицу с Employee
помощью метода, подобногоgetSingleById(id : string) : Employee
Вы слышали о разделении интересов ? Нет, ты будешь сейчас. Это менее строгая версия принципа единой ответственности, в которой говорится, что класс должен иметь только одну ответственность, или, как сказал дядя Боб:
Модуль должен иметь одну-единственную причину для изменения.
Совершенно очевидно, что если бы я смог четко разделить ваш начальный класс на два, которые все еще имеют хорошо округленный интерфейс, то начальный класс, вероятно, делал слишком много, и это было.
Что хорошо в шаблоне репозитория, он не только выступает в качестве абстракции для обеспечения промежуточного уровня между базами данных (который может быть любым, файловым, noSQL, SQL, объектно-ориентированным), но он даже не должен быть конкретным класс. Во многих ОО-языках вы можете определить интерфейс как фактический interface
(или класс с чисто виртуальным методом, если вы находитесь в C ++), а затем иметь несколько реализаций.
Это полностью отменяет решение о том, является ли хранилище фактической реализацией. Вы просто полагаетесь на интерфейс, фактически полагаясь на структуру с interface
ключевым словом. И репозиторий - это именно то, что это причудливый термин для абстракции уровня данных, а именно, отображение данных в ваш домен и наоборот.
Еще одна замечательная особенность разделения его на (по крайней мере) два класса заключается в том, что теперь Employee
класс может четко управлять своими собственными данными и делать это очень хорошо, поскольку ему не нужно заботиться о других сложных вещах.
Вопрос 6: Так что же должен делать конструктор во вновь созданном Employee
классе? Это просто. Он должен принимать аргументы, проверять, являются ли они действительными (например, возраст не должен быть отрицательным, или имя не должно быть пустым), выдавать ошибку, когда данные были недействительными, и если пройденная валидация присваивает аргументы частным переменным сущности. Теперь он не может связаться с базой данных, потому что просто не знает, как это сделать.
Вопрос 4: Нельзя ответить вообще, вообще нет, потому что ответ сильно зависит от того, что именно вам нужно.
Вопрос 5: Теперь, когда вы разделили раздутый класс на два, вы можете иметь несколько методов обновления непосредственно на Employee
классе, как changeUsername
, markAsDeceased
, который будет обрабатывать данные о Employee
классе только в оперативной памяти , и тогда вы могли бы ввести такой метод, как registerDirty
из Шаблон единицы работы для класса репозитория, с помощью которого вы дадите знать хранилищу, что этот объект изменил свойства и его нужно будет обновить после вызова commit
метода.
Очевидно, что для обновления объект должен иметь идентификатор и, следовательно, быть уже сохраненным, и ответственность хранилища заключается в том, чтобы обнаружить это и вызвать ошибку, когда критерии не выполнены.
Вопрос 3: Если вы решите использовать шаблон «Единица работы», create
метод теперь будет registerNew
. Если вы этого не сделаете, я бы назвал это save
вместо этого. Цель репозитория - обеспечить абстракцию между доменом и уровнем данных, поэтому я бы порекомендовал вам, чтобы этот метод (будь то registerNew
или save
) принимал Employee
объект, и это зависит от классов, реализующих интерфейс репозитория, атрибуты которого они решили вывести из сущности. Передача всего объекта лучше, поэтому вам не нужно иметь много дополнительных параметров.
Вопрос 2: Оба метода теперь будут частью интерфейса репозитория, и они не нарушают принцип единой ответственности. Обязанность репозитория состоит в том, чтобы предоставлять CRUD-операции для Employee
объектов, что он и делает (кроме Read и Delete, CRUD преобразуется как в Create, так и в Update). Очевидно, что вы можете разделить репозиторий еще дальше, имея EmployeeUpdateRepository
и так далее, но это редко требуется, и одна реализация обычно может содержать все операции CRUD.
Вопрос 1: В результате вы Employee
получили простой класс, который теперь (среди прочих атрибутов) будет иметь идентификатор. Является ли идентификатор заполненным или пустым (или null
), зависит от того, был ли объект уже сохранен. Тем не менее, идентификатор по-прежнему является атрибутом, которым владеет объект, и ответственность Employee
объекта заключается в том, чтобы заботиться о его атрибутах и, следовательно, заботиться о его идентификаторе.
Независимо от того, имеет ли объект идентификатор или нет, обычно не имеет значения, пока вы не попытаетесь применить к нему некоторую постоянную логику. Как упоминалось в ответе на вопрос 5, хранилище отвечает за обнаружение того, что вы не пытаетесь сохранить уже сохраненную сущность или пытаетесь обновить сущность без идентификатора.
Важная заметка
Пожалуйста, имейте в виду, что, хотя разделение проблем велико, на самом деле разработка функционального слоя репозитория является довольно утомительной работой, и, по моему опыту, сделать ее немного сложнее, чем подход с активной записью. Но в итоге вы получите гораздо более гибкий и масштабируемый дизайн, что может быть полезно.
Employee
объект для предоставления абстракции, вопросы 4. и 5. обычно не отвечают, зависят от ваших потребностей, и если вы разделяете структуру и операции CRUD на два класса, тогда совершенно ясно, что конструкторEmployee
не может получить данные от БД больше, так что отвечает 6.