Czytam ostatnio Clean Code i różne artykuły online o SOLID, a im więcej o tym czytam, tym bardziej czuję, że nic nie wiem.
Załóżmy, że buduję aplikację internetową przy użyciu ASP.NET MVC 3. Powiedzmy, że mam UsersController
taką Create
akcję:
public class UsersController : Controller
{
public ActionResult Create(CreateUserViewModel viewModel)
{
}
}
W tej metodzie działania chcę zapisać użytkownika w bazie danych, jeśli wprowadzone dane są prawidłowe.
Teraz, zgodnie z zasadą pojedynczej odpowiedzialności, obiekt powinien ponosić jedną odpowiedzialność, a ta odpowiedzialność powinna być całkowicie zamknięta w klasie. Wszystkie jego usługi powinny być ściśle dostosowane do tej odpowiedzialności. Ponieważ sprawdzanie poprawności i zapisywanie w bazie danych to dwa odrębne obowiązki, myślę, że powinienem utworzyć osobną klasę, aby obsługiwać je w następujący sposób:
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);
}
}
}
To sprawia, że jakiś sens dla mnie, ale ja nie jestem wcale pewien, że to jest właściwy sposób obsłużyć takie rzeczy. Przykładowo jest całkowicie możliwe, aby przejść nieprawidłową instancję CreateUserViewModel
do IUserService
klasy. Wiem, że mogę skorzystać z wbudowanych DataAnnotations, ale co, jeśli nie są wystarczające? Obraz, który mój ICreateUserValidator
sprawdza w bazie danych, aby sprawdzić, czy jest już inny użytkownik o tej samej nazwie ...
Inną opcją jest pozwolenie na IUserService
zajęcie się walidacją w następujący sposób:
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;
}
}
Ale czuję, że naruszam tutaj zasadę pojedynczej odpowiedzialności.
Jak mam sobie z tym poradzić?
źródło
user
klasa nie powinna obsłużyć weryfikacji? SRP czy nie, nie rozumiem, dlaczegouser
instancja nie powinna wiedzieć, kiedy jest poprawna czy nie, i polegać na czymś innym, aby to ustalić. Jakie inne obowiązki ma klasa? Plus, kiedyuser
zmiany prawdopodobnie się zmienią, więc outsourcing, że do innej klasy stworzy tylko ściśle powiązaną klasę.Odpowiedzi:
Naprawdę nie sądzę, abyś naruszył zasadę pojedynczej odpowiedzialności w swoim drugim przykładzie.
UserService
Klasa ma tylko jeden powód do zmiany: jeśli istnieje potrzeba, aby zmienić sposób zaoszczędzić użytkownikowi.ICreateUserValidator
Klasa ma tylko jeden powód, aby zmienić: jeśli istnieje potrzeba, aby zmienić sposób sprawdzania poprawności użytkownika.Muszę przyznać, że twoje pierwsze wdrożenie jest bardziej intuicyjne. Walidacji powinien jednak dokonać podmiot, który tworzy użytkownika. Sam twórca nie powinien być odpowiedzialny za walidację; powinien raczej przekazać odpowiedzialność klasie walidacyjnej (jak w twojej drugiej implementacji). Nie sądzę więc, aby w drugim projekcie brakowało SRP.
źródło
Wydaje mi się, że pierwszy przykład jest „bliższy” prawdziwemu SRP; W twoim przypadku kontroler ma obowiązek wiedzieć, jak to załatwić i jak przekazać ViewModel.
Czy nie ma większego sensu, aby cały IsValid / ValidationMessages był częścią samego ViewModel? Nie zajmowałem się MVVM, ale wydaje się, że jest to stary wzorzec Model-View-Presenter, w którym Prezenter mógł sobie poradzić z takimi rzeczami, ponieważ był on bezpośrednio związany z prezentacją. Jeśli Twój ViewModel może sam sprawdzić poprawność, nie ma szans na przesłanie nieprawidłowego do metody CreateService's.
źródło
IsValid
logiczną iValidationMessages
tablicę, nadal może wywoływać klasę Validator i nie musi się martwić o sposób implementacji sprawdzania poprawności. Kontroler może najpierw sprawdzić, czy npif (userViewModel.IsValid) { userService.Create(userViewModel); }
. ViewModel powinien wiedzieć, czy jest poprawny, ale nie skąd wie, że jest poprawny.