Код рефакторинга, чтобы избежать
У меня есть проект BusinessLayer, который имеет следующий код. Объектом домена является FixedBankAccount (который реализует IBankAccount).
-
Репозиторий создается как общедоступное свойство объекта домена и создается как член интерфейса. Как реорганизовать его так, чтобы repository не был членом интерфейса ?
-
Объект домена (FixedBankAccount) использует repository непосредственно для хранения данных. Является ли это нарушением принципа единой ответственности? Как это исправить?
Примечание. Шаблон репозитория реализуется с использованием LINQ to SQL.
РЕДАКТИРОВАТЬ
Является ли приведенный ниже код более подходящим? https://codereview.stackexchange.com/questions/13148/is-it-good-code-to-satisfy-single-responsibility-principle
КОД
public interface IBankAccount { RepositoryLayer.IRepository AccountRepository { get; set; } int BankAccountID { get; set; } void FreezeAccount(); }
public class FixedBankAccount : IBankAccount { private RepositoryLayer.IRepository accountRepository; public RepositoryLayer.IRepository AccountRepository { get { return accountRepository; } set { accountRepository = value; } } public int BankAccountID { get; set; } public void FreezeAccount() { ChangeAccountStatus(); } private void SendEmail() { } private void ChangeAccountStatus() { RepositoryLayer.BankAccount bankAccEntity = new RepositoryLayer.BankAccount(); bankAccEntity.BankAccountID = this.BankAccountID; accountRepository.UpdateChangesByAttach(bankAccEntity); bankAccEntity.Status = "Frozen"; accountRepository.SubmitChanges(); } }
public class BankAccountService { RepositoryLayer.IRepository accountRepository; ApplicationServiceForBank.IBankAccountFactory bankFactory; public BankAccountService(RepositoryLayer.IRepository repo, IBankAccountFactory bankFact) { accountRepository = repo; bankFactory = bankFact; } public void FreezeAllAccountsForUser(int userId) { IEnumerable accountsForUser = accountRepository.FindAll(p => p.BankUser.UserID == userId); foreach (RepositoryLayer.BankAccount repositroyAccount in accountsForUser) { DomainObjectsForBank.IBankAccount acc = null; acc = bankFactory.CreateAccount(repositroyAccount); if (acc != null) { acc.BankAccountID = repositroyAccount.BankAccountID; acc.accountRepository = this.accountRepository; acc.FreezeAccount(); } } } }
public interface IBankAccountFactory { DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount); }
public class MySimpleBankAccountFactory : IBankAccountFactory { public DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount) { DomainObjectsForBank.IBankAccount acc = null; if (String.Equals(repositroyAccount.AccountType, "Fixed")) { acc = new DomainObjectsForBank.FixedBankAccount(); } if (String.Equals(repositroyAccount.AccountType, "Savings")) { acc = new DomainObjectsForBank.SavingsBankAccount(); } return acc; } }
ЧТЕНИЕ:
-
DDD – переход состояния состояния
-
https://codereview.stackexchange.com/questions/13148/is-it-good-code-to-satisfy-single-responsibility-principle
-
Использование «принципа единой ответственности» заставляет мои контейнеры иметь публичные сеттеры
-
https://softwareengineering.stackexchange.com/questions/150760/single-responsibility-principle-how-can-i-avoid-code-fragmentation
- Может ли область переменной Jinja расширяться во внутреннем блоке?
- Лучший способ избежать невидимости публичного участника и разрастания / повторения исходного кода с унаследованными шаблонами classов?
- Как работает `is_base_of`?
- Disambiguate перегруженный указатель функции участника передается как параметр шаблона
- перегрузка оператора друга << для шаблона classа
- Выделить всю строку TreeViewItem в WPF
- Можно ли эмулировать шаблон ?
- Как я могу переопределить шаблон @ Html.LabelFor?
Я бы не сказал, что это анти-шаблон, поскольку анти-шаблон должен быть образцом в первую очередь (узнаваемый, широко распространенный способ делать вещи), и я не знаю ни о каком «репозитории в -Домен-объект “.
Тем не менее, это, безусловно, неправильная практика ИМО, потому что ваш объект домена BankAccount смешивает 3 обязанности:
-
Его естественная и законная ответственность как объект домена, чтобы замерзнуть и изменить свой статус.
-
Ответственность за обновление и предоставление изменений в постоянном хранилище (с использованием accountRepository).
-
Ответственность за решение о том, как отправить сообщение (в этом случае, по электронной почте) и отправить его.
В результате ваш объект домена тесно связан со слишком многими вещами, делая его жестким и хрупким. Это может измениться и, возможно, сломать слишком много причин.
Таким образом, нет анти-шаблона, но, безусловно, является нарушением принципа единой ответственности .
Последние две обязанности должны быть перенесены на отдельные объекты. Отправка изменений скорее принадлежит объекту, который управляет бизнес-транзакцией (Единица работы), и знает о правильном времени для завершения транзакции и операций с флешем. Второй может быть помещен в EmailService на уровне инфраструктуры. В идеале, объект, выполняющий глобальную операцию Freeze, не должен знать механизм доставки сообщений (по почте или что-то еще), но вместо этого его следует вводить, что обеспечит большую гибкость.
Рефакторинг этого кода так, что repository не является членом интерфейса, достаточно прост. Репозиторий – это зависимость реализации, а не интерфейса – вставляйте его в свой конкретный class и удаляйте его из IBankAccount.
public class FixedBankAccount : IBankAccount { public FixedBankAccount(RepositoryLayer.IRepository accountRepository) { this.accountRepository = accountRepository; } private readonly RepositoryLayer.IRepository accountRepository; public int BankAccountID { get; set; } public void FreezeAccount() { ChangeAccountStatus(); } private void SendEmail() { } private void ChangeAccountStatus() { RepositoryLayer.BankAccount bankAccEntity = new RepositoryLayer.BankAccount(); bankAccEntity.BankAccountID = this.BankAccountID; accountRepository.UpdateChangesByAttach(bankAccEntity); bankAccEntity.Status = "Frozen"; accountRepository.SubmitChanges(); } }
Что касается второго вопроса …
Да, объект домена нарушает SRP, зная ваш код персистентности. Однако это может быть или не быть проблемой; многие структуры смешивают эти обязанности с большим эффектом – например, шаблон Active Record. Это делает модульное тестирование немного более интересным, поскольку оно требует от вас фальсификации вашего IRepository.
Если вы решите иметь более устойчивый домен, вы, вероятно, лучше всего это сделаете, выполнив шаблон «Единица работы». Загруженные / отредактированные / удаленные экземпляры регистрируются в Единице работы, которая несет ответственность за сохраняющиеся изменения в конце транзакции. Блок обработки отвечает за отслеживание изменений.
Как это зависит от типа создаваемого приложения и используемых вами инструментов. Я считаю, что, если вы работаете с Entity Framework, вы, возможно, сможете использовать DataContext как свою единицу работы. (Есть ли у Linq-to-SQL понятие DataContext?)
Ниже приведен пример шаблона «Единица работы» с Entity Framework 4.
Решение Remi намного лучше, но лучшим решением IMO было бы следующее:
1- Не вводите ничего в объекты домена: вам не нужно вводить что-либо в свои объекты домена. Не все службы. Не репозитории. Ничего. Просто доброкачественная модель домена
2- Пусть прямые репозитории уровня сервиса, чтобы сделать SubmitChanges, … но имейте в виду, что уровень сервиса должен быть тонким, а объекты домена не должны быть анемичными
Интерфейсы не имеют прямого отношения к принципу единой ответственности. Вы не можете полностью отделить код доступа к данным от бизнес-логики – они должны общаться в какой-то момент! То, что вы хотите сделать, сводится к минимуму (но не избегает ), когда это происходит. Убедитесь, что ваша схема базы данных логически не физическая (т. Е. На основе предикатов, а не таблиц и столбцов) и что код на основе реализации (например, драйверы подключения к системе управления базами данных) находится только в одном месте – class, ответственный за разговор с firebase database. Каждый объект должен быть представлен одним classом. Вот и все.