S.O.L.I.D - 5 principii importante ale programarii orientate pe obiecte

S.O.L.I.D este un acronim pentru primele 5 principii ale design-ului orientat pe obiecte (OOD), dupa cum spune Robert C. Martin, cunoscut si ca Uncle Bob.

Aceste principii, combinate, ajuta programatorii in dezvoltarea de software ajutandu-i sa isi faca aplicatia usor de mentenat si extins. De asemenea, ajuta atat la scrierea de cod cat mai curat intr-un timp cat mai scurt.

S.O.L.I.D vine de la:

  • S - Single responsability principle
  • O - Open-Close Principle
  • L - Liskov substitution principle
  • I - Interface segregation principle
  • D - Dependency Injection Principle

In acest articol am sa iau pe rand fiecare principiu si am sa spun cateva vorbe despre el si sa dau si cateva exemple, urmand ca in viitorul apropiat (probabil) sa scriu cate un articol detaliat despre fiecare dintre ele cu cat mai multe exemple.

Single Responsability Principle

Acest prim principiu din SOLID ne spune ca o clasa trebuie sa aiba o singura responsabilitate. O buna practica de urmat este aceea de a lista responsabilitatile clasei in doc-block, astfel incat sa iti amintesti (de asemenea, sa vada si ceilalti programatori cu care lucrezi) care este scopul acelei clase:

app/EmptyGarden.php

<?php

class EmptyGarden
{
    private $width;
    private $height;

    public function __construct($width, $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    public function items()
    {
        $numberOfSpots = ceil($this->width * $this->height);
        return array_fill(0, $numberOfSpots, 'dirt');
    }
}

La o simpla privire, ne-am putea gandi ca pe langa metoda items(), am putea avea tot in clasa asta si metoda harvest(), are sens, nu? Ei bine, in contextul dat ne putem da seama ca nu are sens ca o gradina sa se culeaga singura, mai de graba un fermier ar putea culege ce avem in gradina. Daca am adauga metoda harvest() in aceasta clasa ar insemna sa ii mai dam inca o responsabilitate.

Open/Closed Principle

$garden = MarijuanaGarden(10, 10);
$garden->items(); // as zice ca asa arata o zi normala pentru Snoop Dogg

Stiu, acum imi veti zice "MarijuanaGarden? De ce nu ai instantiat tot un obiect de tipul EmptyGarden caruia sa ii pasezi un parametru 'Marijuana' care sa reprezinte tipul?". Stiu la ce va ganditi, mai putine clase despre care sa iti faci griji, mai putine probleme. Cu siguranta, mai putine clase, dar asta ar insemna ca la un momentdat vezi avea in mare if/else sau un switch (nu am sa discut acum de ce nu imi palce switch-ul, cred ca merita un articol numai pentru el), ceea ce clar ar duce la necesitatea de a modifica EmptyGarden de fiecare data cand trebuie sa adaugam sau sa stergem un tip de gradina.

O-ul din SOLID vine de le open/close principle care ne spune ca o clasa trebuie sa poata fi extinsa oricand este nevoie, dar nu trebuie modificata.

<?php

class MaijuanaGarden extends EmptyGarden
{
    public function items()
    {
        $numberOfSpots = ceil($this->width, $this->height);
        return array_fill(0, $numberOfSpots, 'weed');
    }
}

Liskov Substitution Principle

Acest principiu se refera la Type Hinting si la faptul ca un obiect poate fi inlocuit de orice subtip al acestuia fara a genera probleme in modul de functionare.
De exemplu, ce s-ar intampla daca, atunci can instantiem un nou obiect de tip EmptyGarden dam ca si parametru un string in loc de int?

new EmptyGarden("bacon", -1);

Aceasta instantiere ne va aduce probleme. Hai, in loc sa initializam clasa cu 2 int-uri, sa o initilizam cu o noua clasa ce implementeaza interfata PlotArea.

public function __construct(PlotArea $plot)
{
    $this->plot = $plot;
}

Iar aceasta este interfata:

<?php

interface PlotArea{
    public function totalNumberOfPlots();
}

Astfel incat clasele ce implementeaza PlotArea vor sti dimensiunea gradinii. De exemplu am putea avea o clasa RectangleArea care sa aiba o dimensiune de 10 cu 10.
Dupa aceste modificari, trebuie sa schimbam si clasa MarijuanaGarden.

<?php

class EmptyGarden
{
    private $plot;

    public function __construct(PlotArea $plot)
    {
        $this->plot = $plot;
    }

    public function items()
    {
        $numberOfSpots = $this->plot->totalNumberOfPlots();
        return array_fill(0, $numberOfSpots, 'dirt');
    }
}

Dar, cum putem instantia clasa EmptyGarden acum, avand in vedere ca in clipa de fata PlotArea este o interfata, nu o clasa? Hai sa definim clasa RectangleArea care sa implementeze interfata.

Dupa cum avem codul scris acum, putem observa ca o gradina de 10 cu 10 are de fapt 50 de bucati.

De asemenea, metodele noastre ar trebui sa aiba si return type-uri, in limbaje strongly typed, precum Java asta este ceva comun, dar in PHP, un limbaj care nu impune acest lucru este un lucru ce trebuia mentionat (atentie, daca folositi PHP 5 nu aveti aceasta optiune, fiind ceva adaugat in PHP 7).

Interface Segregation Principle

Acum, ne gandim ca vrem sa adaugam mai multe functionalitati pentru noua noastra gradine, de exemplu, vrem sa crestem plante in gradina.

<?php 

interface GardenInterface{
    public function grow($advanceNumberOfDays);
}

Ok, arata destul ok pana acum, nu? Dar sa nu uitam ca mai sunt o multime de responsabilitati pe care gradina noastra ar trebui sa le aiba:

<?php 

interface GardenInterface{
    public function grow($advanceNumberOfDays);
    public function weed($pickOutPercentage);
    public function pestAttack($attackFactor);
    public function water($inGallons);
    public function sunshine($radiationLevel);
    public function fertilize($type, $amount);
}

Holy methods, Batman!
Cam multe responsabilitati, nu? Se poate observa clar, cu cat interfata noastra se mareste cu atat mai multe responsabilitati va avea orice clasa o implementeaza, ceea ce ne duce cu gandul la primul principiu, SingleResponsability Principle.
Aici, problema este ca adaugam din ce in ce mai mutla responsabilitate intr-o singura clasa, lucru ca o va face din ce in ce mai greu de controlat. In cazul de fata, trebuie sa encapsulam fiecare responasbilitate astfel incat sa o folosim numai cand este nevoie.

Hai sa ne gandim in felul urmator, avem interfata GardenInterface care implementeaza interferetele necesare :

<?php 

interface GardenInterface implements GrowableInterface, WeedableOnterface{
    
}

Avand o interfata care implementeaza fix ce ii trebuie ne da mult mai mutla flexibilitate pentru ca ne permite sa alegem fix functionalitatile necesare clasei noastre concrete.

Dependency Inversion Principle

Acest principiu ne spune ca o clasa trebuie sa depinda de abastractizari are altor clase, nu de alte clase concrete. Ce inseamna asta?  Simplu, daca iti aduci aminte de mai devreme, cand am dat ca type hint interfata PlotArea? Ce se intampla daca in schimb dadeam ca parametru clasa concreta RectangleArea?

    public function __construct(RectangleArea $plot)
    {
        $this->plot = $plot;
    }

Acest lucru ne-ar fi fortat, ca de fiecare data cand instantiem EmptyGarden sa folosim ca si parametru RectangleArea, pe cand dand ca si type hint interfata putem da ca parametru orice clasa care implementeaza acea interfata.

Probabil ati auzit de expresia asta: low coupling, high coheision. Coupling se refera la cat de mult o clasa se bazeaza pe alte clase, iar cohesion la cat de mult elementele dintr-o clasa isi au locul acolo. Puteti sa va ganditi la urmatorul exemplu, pornind de la premiza cum ca aceasta clasa a noastra este o insula, ne dorim ca totul sa functioneze bine in interior (economia, justitia, etc), dar nu ne dorim ca insula noastra sa depinde de alte tari, vrem ca aceste dependinte din afara sa fie minime.

Concluzii

Sumarizand ce am zis pana acum:

Single Responsability Principle
Nu iti pune toate ouale intr-un singur cos. O clasa trebuie sa aiba o singura responsabilitate.

Open/Closed Principle
Nu schimba aceasi clasa de fiecare data cand apare ceva nou. Daca se intampla asta, abstractizeaza ce se schimba.

Liskov Substitution Principle
Return type pentru acelasi tip, ca in clasa parinte, intr-o subclasa suprascrisa.

Interface Segregation Principle
Nu scrie interfete cu multe metode (as zice maxim 5). Asta este un semn clar ca faci mult prea multe lucruri intr-un singur loc.

Dependency Inversion Principle
Bazeaza-te pe clase abstracte si interfete mai mult decat te bazezi pe clase concrete. Asta iti va da flexibilitate.

Spor!

 

Avem un cod de conduita.
Folosim cookie-uri pentru a oferi functionalitatile critice ale aplicatiei Invata-Programare. Folosim cookie-uri si pentru a analiza traficul, pentru care e nevoie de consimtamantul dvs. explicit.