Kiedy postępuję zgodnie z SRP, jak powinienem postępować z zatwierdzaniem i zapisywaniem jednostek?

10

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 UsersControllertaką Createakcję:

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ę CreateUserViewModeldo IUserServiceklasy. Wiem, że mogę skorzystać z wbudowanych DataAnnotations, ale co, jeśli nie są wystarczające? Obraz, który mój ICreateUserValidatorsprawdza w bazie danych, aby sprawdzić, czy jest już inny użytkownik o tej samej nazwie ...

Inną opcją jest pozwolenie na IUserServicezaję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ć?

Kristof Claes
źródło
Czy userklasa nie powinna obsłużyć weryfikacji? SRP czy nie, nie rozumiem, dlaczego userinstancja nie powinna wiedzieć, kiedy jest poprawna czy nie, i polegać na czymś innym, aby to ustalić. Jakie inne obowiązki ma klasa? Plus, kiedy userzmiany prawdopodobnie się zmienią, więc outsourcing, że do innej klasy stworzy tylko ściśle powiązaną klasę.
sebastiangeiger

Odpowiedzi:

4

Naprawdę nie sądzę, abyś naruszył zasadę pojedynczej odpowiedzialności w swoim drugim przykładzie.

  • UserServiceKlasa ma tylko jeden powód do zmiany: jeśli istnieje potrzeba, aby zmienić sposób zaoszczędzić użytkownikowi.

  • ICreateUserValidatorKlasa 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.

Guven
źródło
1
Wzór pojedynczej odpowiedzialności? Może zasada?
yannis,
Tak, oczywiście :) Naprawiłem to!
Guven,
0

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.

Wayne Molina
źródło
Zawsze myślałem, że ViewModels powinny być prostymi DTO bez zbytniej logiki. Nie jestem pewien, czy powinienem umieścić coś takiego jak sprawdzenie bazy danych w modelu ViewModel ...
Kristof Claes,
Myślę, że będzie to zależeć od tego, co pociąga za sobą wasza walidacja; jeśli ViewModel po prostu ujawnia wartość IsValidlogiczną i ValidationMessagestablicę, 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 np if (userViewModel.IsValid) { userService.Create(userViewModel); }. ViewModel powinien wiedzieć, czy jest poprawny, ale nie skąd wie, że jest poprawny.
Wayne Molina,