В последнее время я читал « Чистый код» и различные онлайн-статьи о SOLID, и чем больше я читаю об этом, тем больше чувствую, что ничего не знаю.
Допустим, я создаю веб-приложение с использованием ASP.NET MVC 3. Допустим, у меня есть действие UsersController
с таким Create
действием:
public class UsersController : Controller
{
public ActionResult Create(CreateUserViewModel viewModel)
{
}
}
В этом методе действия я хочу сохранить пользователя в базе данных, если введенные данные верны.
Теперь, согласно принципу единой ответственности, объект должен нести единственную ответственность, и эта ответственность должна быть полностью заключена в класс. Все его услуги должны быть тесно связаны с этой ответственностью. Поскольку проверка и сохранение в базе данных являются двумя отдельными обязанностями, я думаю, мне следует создать отдельный класс для их обработки следующим образом:
public class UsersController : Controller
{
private ICreateUserValidator validator;
private IUserService service;
public UsersController(ICreateUserValidator validator, IUserService service)
{
this.validator = validator;
this.service= service;
}
public ActionResult Create(CreateUserViewModel viewModel)
{
ValidationResult result = validator.IsValid(viewModel);
if (result.IsValid)
{
service.CreateUser(viewModel);
return RedirectToAction("Index");
}
else
{
foreach (var errorMessage in result.ErrorMessages)
{
ModelState.AddModelError(String.Empty, errorMessage);
}
return View(viewModel);
}
}
}
Это имеет некоторый смысл для меня, но я совсем не уверен, что это правильный способ справиться с такими вещами. Это, например , вполне возможно передать недопустимый экземпляр CreateUserViewModel
в IUserService
классе. Я знаю, что могу использовать встроенные аннотации данных, но что, если их недостаточно? Изображение, которое my ICreateUserValidator
проверяет базу данных, чтобы увидеть, есть ли уже другой пользователь с таким же именем ...
Другой вариант - позволить IUserService
проверке пройти проверку следующим образом:
public class UserService : IUserService
{
private ICreateUserValidator validator;
public UserService(ICreateUserValidator validator)
{
this.validator = validator;
}
public ValidationResult CreateUser(CreateUserViewModel viewModel)
{
var result = validator.IsValid(viewModel);
if (result.IsValid)
{
// Save the user
}
return result;
}
}
Но я чувствую, что нарушаю принцип единой ответственности здесь.
Как я должен иметь дело с чем-то вроде этого?
источник
user
класс не должен обрабатывать проверку? SRP или нет, я не понимаю, почемуuser
экземпляр не должен знать, когда он действителен или нет, и полагаться на что-то другое, чтобы определить это для него. Какие другие обязанности у класса? Кроме того, приuser
изменении валидация, вероятно, изменится, поэтому передача на аутсорсинг другому классу создаст только тесно связанный класс.Ответы:
Я действительно не думаю, что вы нарушаете принцип единой ответственности во втором примере.
У
UserService
класса есть только одна причина для изменения: если есть необходимость изменить способ сохранения пользователя.У
ICreateUserValidator
класса есть только одна причина для изменения: если есть необходимость изменить способ проверки пользователя.Я должен признать, что ваша первая реализация более интуитивна. Однако проверка должна выполняться объектом, который создает пользователя. Сам создатель не должен нести ответственность за проверку; он должен скорее делегировать ответственность классу валидатора (как в вашей второй реализации). Так что я не думаю, что второй дизайн не имеет SRP.
источник
Мне кажется, что первый пример «ближе» к истинному SRP; В вашем случае контроллер обязан знать, как соединить вещи и как передать ViewModel.
Разве не имеет смысла, чтобы все сообщения IsValid / ValidationMessages были частью самой ViewModel? Я не баловался с MVVM, но похоже на старый шаблон Model-View-Presenter, где Presenter мог нормально обрабатывать такие вещи, потому что он был напрямую связан с презентацией. Если ваша ViewModel может проверить себя на достоверность, нет возможности передать неверный метод в метод UserService Create.
источник
IsValid
логическое значение иValidationMessages
массив, он все равно может вызывать класс Validator и не должен беспокоиться о том, как выполняется проверка. Контроллер может проверить , что первый , например , Вif (userViewModel.IsValid) { userService.Create(userViewModel); }
основном ViewModel должны знать , если это действительно, но не как он знает , что это действует.