Devloger

SOLID - SRP - Reguła Pojedynczej Odpowiedzialności

SOLID - SRP - Reguła Pojedynczej Odpowiedzialności

Zasady SOLID - SRP - Pojedyncza odpowiedzialność

W dzisiejszym artykule poruszę temat niesamowicie ważny. Otóż omówię zagadnienie zbioru zasad o nazwie SOLID oraz jego S w nazwie za którym stoi reguła Single Responsibility – pojedynczej odpowiedzialności.

Czym zatem są zasady SOLID?

Pięć zasad SOLID

Zasady SOLID to pięć podstawowych (fundamentalnych) reguł programowania obiektowego. Gdzie każda litera w nazwie odnosi się do jednej z tych zasad (stąd SOLID).

Wszystkie są niezmiernie ważne i wszystkie się łączą. Wzajemnie się wspierają.

Występuje tutaj pełna synergia między wszystkimi regułami, albowiem wszystkie odnoszą się do jednego - fundamentalnej struktury naszego kodu Emotikon uśmiechniętej buźki.

Jedna wynika z drugiej

Złamanie jednej może w konsekwencji wiązać się z łamaniem pozostałych.

Powinniśmy ich zawsze przestrzegać, ponieważ są one fundamentem struktury naszej aplikacji. A przecież nie chcemy by nasza aplikacja się zawaliła, by była problematyczna i pełna mankamentów.

Te proste reguły pomagają trzymać kod ryzach, chronią aplikację przed szeregiem możliwych błędów i ograniczeń.

SRP - Single Responsibility - Pojedyncza Odpowiedzialność

Zasada ta mówi nam, że każda klasa powinna mieć tylko i wyłącznie jedną odpowiedzialność. Powinna zajmować się tylko jedną ideą. Powinna odpowiadać tylko i wyłącznie za jedną (jakąś konkretną) logikę w naszym systemie.

Co za tym idzie - klasa powinna mieć tylko i wyłącznie jeden powód do zmiany. Tylko i wyłącznie zmiana jakiejś jednej idei powinna ciągnąć za sobą zmianę specyfikacji danej klasy. Tylko jednej. Nigdy nie powinien istnieć więcej niż jeden powód do modyfikacji klasy.

Coś więcej

Reguła ta jest przecież naturalna - mamy wydzielone części w naszej aplikacji, niczym pracowników (specjalistów) odpowiedzialnych za bardzo konkretne zadania. Tak jak w rzeczywistości - ktoś od wszystkiego to ktoś od niczego.

Rozdzielajmy zależności. Przydzielajmy konkretne zadania konkretnym klasom. Nie róbmy wszystkiego w jednej klasie. Tak samo nie róbmy wielu rzeczy w jednej klasie (nienależących do tej samej logiki). Wszystkie nasze klasy powinny być konkretne, a my powinniśmy je konkretyzować.

Przykład

<?php
	
namespace Acme;

use Auth;
use DB;

class SalesReporter
{
    public function between($startDate, $endDate)
    {
        // Perform authentication
        if (! Auth::check()) {
            throw new \Exception('Authentication required for reporting!');
        }

        // get sales from db
        $sales = $this->queryDBForSales($startDate, $endDate);

        // return results
        return $this->format($sales);
    }

    protected function queryDBForSales($startDate, $endDate)
    {
        return DB::table('sales')->whereBetween('created_at', [$startDate, $endDate])->sum('charge') / 100;
    }

    protected function format($sales)
    {
        return '<h1>Sales: ' . $sales . '</h1>';
    }
}

Prosta klasa w której reguła SRP została złamana wiele razy

Albowiem autoryzacja nie jest odpowiedzialnością tej klasy. Dlaczego tak klasa ma w ogóle obsługiwać autoryzację? Tej logiki nie powinno w ogóle tutaj być.

Tak samo metoda queryDBForSales – dlaczego klasa ta ma być odpowiedzialna za niemalże bezpośrednią komunikację z bazą danych? Nie powinny ją interesować takie szczegóły. Powinna skorzystać z metody poprzez klasę do tego przeznaczoną.

I dlaczego ta klasa zajmuje się formatowaniem danych (w tym przypadku w postaci HTML)? To nie jest jej odpowiedzialność i zmartwienie. Tutaj również powinna być delegacja do klasy która jest za to odpowiedzialna. Lub pozbycie się formatowania i po prostu zwrócenie danych a formatowanie zostawić użytkownikowi klas.

I co w sytuacji gdy będziemy chcieli zmienić sposób autoryzacji, warstwę danych lub formatowanie? Za każdym razem ta klasa będzie musiała ulec zmianie!

A zmiana w tej klasie powinna nastąpić tylko i wyłącznie w momencie w którym zmieniona zostanie ta konkretna logika za którą jest odpowiedzialna ta klasa.

Tak powinna ta klasa wyglądać

<?php

namespace Acme;

use Acme\SalesOutputInterface;
use Acme\Repositories\SalesRepository;

class SalesReporter
{
	protected $repo;
	protected $formatter;

	public function __construct(SalesRepository $repo, SalesOutputInterface $formatter)
	{
		$this->repo = $repo;
		$this->formatter = $formatter;
	}

	public function between($startDate, $endDate)
	{
		$sales = $this->repo->between($startDate, $endDate);

		return $this->formatter->output($sales);
	}
}

To wszystko

Reguła jakże prosta a stosowanie się do niej znacząco zwiększa wartość i jakość kodu. Jest to najpowszechniejsza reguła ze wszystkich. Jednak często nie jest przestrzegana – jak pozostałe zresztą też.

Pamiętaj!

Każda klasa musi mieć jedną i tylko jedną odpowiedzialność. Oraz jeden i tylko jeden powód do zmiany Emotikon uśmiechniętej buźki!

Dziękuję za lekturę i zapraszam do komentowania oraz innych moich artykułów. Dobrego dnia, bywaj!

Krystian Bogucki

Podobał Ci się ten artykuł?

Jeśli tak, to zarejestruj się aby otrzymywać powiadomienia o nowych artykułach. Nie ujawnię nikomu Twojego adresu!