Zasada Pojedynczej Odpowiedzialności dotyczy tego, że Twój kod wykonuje tylko jedną czynność i możesz podzielić całą funkcjonalność na kilka klas, z których wszystkie przeznaczone są do wykonania jednej konkretnej rzeczy. Przykładem jest konkretna klasa do sprawdzania poprawności, wykonywania logiki biznesowej, wzbogacania modelu, pobierania danych, aktualizowania danych, nawigacji itp.
Separation of Concerns dotyczy tego, że Twój kod nie jest ściśle powiązany z niektórymi innymi klasami / systemami. Używanie interfejsów w kodzie bardzo pomaga, w ten sposób możesz luźno połączyć klasy / systemy z kodem. Zaletą tego jest to, że łatwiej jest również przetestować kod jednostkowo. Istnieje wiele frameworków (IoC), które mogą ci w tym pomóc, ale oczywiście możesz sam to zaimplementować.
Przykład czegoś SoC, ale bez SRP
public class Foo
{
private readonly IValidator _validator;
private readonly IDataRetriever _dataRetriever;
public Foo(IValidator validator, IDataRetriever dataRetriever)
{
_validator = validator;
_dataRetriever = dataRetriever;
}
public NavigationObject GetDataAndNavigateSomewhereIfValid()
{
var data = _dataRetriever.GetAllData();
if(_validator.IsAllDataValid(data))
{
object b = null;
foreach (var item in data.Items)
{
b = DoSomeFancyCalculations(item);
}
if(_validator.IsBusinessDataValid(b))
{
return ValidBusinessLogic();
}
}
return InvalidItems();
}
private object DoSomeFancyCalculations(object item)
{
return new object();
}
private NavigationObject ValidBusinessLogic()
{
return new NavigationObject();
}
private NavigationObject InvalidItems()
{
return new NavigationObject();
}
}
Jak widać, ten kod nie jest ściśle powiązany z klasami lub innymi systemami, ponieważ używa tylko niektórych interfejsów do robienia różnych rzeczy. Jest to dobre z punktu widzenia SoC.
Jak widać, klasa ta zawiera również 3 prywatne metody, które wykonują wymyślne rzeczy. Z punktu widzenia SRP metody te powinny być prawdopodobnie umieszczone w niektórych własnych klasach. 2 z nich robi coś z nawigacją, która pasowałaby do klasy INavigation. Drugi wykonuje kilka fantazyjnych obliczeń na przedmiocie, prawdopodobnie można go umieścić w klasie IBusinessLogic.
Mając coś takiego, oboje macie SoC i SRP na miejscu:
public class Foo
{
private readonly IValidator _validator;
private readonly IDataRetriever _dataRetriever;
private readonly IBusinessLogic _businessLogic;
private readonly INavigation _navigation;
public Foo(IValidator validator, IDataRetriever dataRetriever, IBusinessLogic businessLogic, INavigation navigation)
{
_validator = validator;
_dataRetriever = dataRetriever;
_businessLogic = businessLogic;
_navigation = navigation;
}
public NavigationObject GetDataAndNavigateSomewhereIfValid()
{
var data = _dataRetriever.GetAllData();
if(_validator.IsAllDataValid(data))
{
object b = null;
foreach (var item in data.Items)
{
b = _businessLogic.DoSomeFancyCalculations(item);
}
if(_validator.IsBusinessDataValid(b))
{
return _navigation.ValidBusinessLogic();
}
}
return _navigation.InvalidItems();
}
}
Oczywiście możesz debatować, czy cała ta logika powinna być umieszczona w GetDataAndNavigateSomewhereIfValid
metodzie. To jest coś, o czym powinieneś sam zdecydować. Dla mnie wygląda na to, że ta metoda robi zbyt wiele rzeczy.