Это более правильная запись моего первоначального комментария под вашим вопросом. Ответы на вопросы, заданные ФП, можно найти внизу этого ответа. Также, пожалуйста, проверьте важную заметку, расположенную в том же месте.
То, что вы сейчас описываете, 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.