Prinicipi di sviluppo SOLID in WinterCMS - S

Luca Benati

In questo articolo vediamo il principio di Singola responsabilità applicato in Winter


Pubblicato da Luca Benati il 31 agosto 2022

Dato che non basta sapere fare rispondere una route a un metodo per poter dire di stare facendo bene in questa serie di articoli vediamo i principi base della programmazione ad oggetti applicati nel nostro framework di sviluppo preferito WinterCMS!

Per chi non ne ha mai sentito parlare SOLID è l'acronimo dei cinque principi fondamentali della programmazione ad oggetti, citando Wikipedia

In informatica, e in particolare in programmazione, l'acronimo SOLID si riferisce ai "primi cinque principi" dello sviluppo del software orientato agli oggetti descritti da Robert C. Martin in diverse pubblicazioni dei primi anni 2000. Tali principi vengono detti SOLID principles

Gli scopi fondamentali di questi principi sono:

  • Scrivere codice più facile da mantenere
  • Scrivere codice più semplice da leggere e comprendere
  • Rendere il codice più semplice facilitando l'estendibilità del sistema con nuove funzionalità senza "rompere" e interferire con quelle esistenti
  • Scrivere codice più pulito

Scriveremo un articolo per principio, quindi partiamo dall'inizio e cominicamo con la "S" di Single Responsability

Single Responsability

A class should have one and only one reason to change. In other words, every class should have only one responsibility

Come leggiamo dalla sua definizione una classe dovrebbe avere solo una ragione per cambiare ed una sola responsabilità.

Con singola responsabilità non si intende che una classe deve fare solo una cosa altrimenti ci troveremo in una selva di classi, ma che essa riferisce ad un solo client che è l'unico responsabile delle sue eventuali modifiche.

Vediamo un esempio; di seguito abbiamo la classe OrdersReport che espone il metodo getOrders() il quale ritorna un elenco degli ordini effettuati in un dato range temporale, il metodo viene chiamato ad esempio da una route in questo modo:

Esempio di cattiva pratica



Route::get('/ordini', function(){
   $report = new \Mio\Plugin\Reports\OrdersReport();
   $startDate = Carbon\Carbon::now()->subDays(10);
   $endDate = Carbon\Carbon::now();

   return $report->getOrders($startDate, $endDate);
});



namespace Mio\Plugin\Reports;

class OrdersReport
{
    public function getOrders($startDate, $endDate)
    {
        $orders = $this->queryDBForOrders($startDate, $endDate);

        return $this->format($orders);
    }

    protected function queryDBForOrders($startDate, $endDate)
    {
        return DB::table('orders')->whereBetween('created_at', [$startDate, $endDate])->get();
    }

    protected function format($orders)
    {
        return '

Orders: ' . $orders . '

'; } }

Questo è un classico esempio di cattiva pratica di programmazione che viola il principio di singola resposabilità dato che la classe si occupa oltre che a ritornare il dato anche di interrogare il database per recuperare le infomrazioni e di formattare il risultato.

Implementiamo il tutto ora rispettando il principio di Singola responsabilità, per fare questo andremo a spostare l'interrogazione del database nella classe OrdersRepository e per la formattazione ci avvaleremo della classe HtmlOutput che implementa l'interfaccia OredersOutPutInterface, il repository degli ordini li andremo ad iniettare nella classe OrdersReport tramite il costruttore mentre in questo modo:

Ridefiniamo la classe OrdersReport dichiarando due proprietà protette che andremo a valorizzare nel costruttore con le due istanze di classe

Invece che fare l'hinting con la classe HtmlOutput usiamo l'interfaccia in questo modo sarà possibile passare al costruttore qualsiasi classe che la implementi perchè di conseguenza avrà disponibile il metodo output come da contratto (Rivedremo questo concetto in uno dei prossimi articoli della serie).

Esempio di buona pratica



namespace Mio\Plugin\Reports;

use Mio\Plugin\Repositories\OrdersRepository;
use Mio\Plugin\Interfaces\OrdersOutPutInterface;

class OrdersReport
{
    protected $repo;
    protected $formatter;

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

    public function getOrders($startDate, $endDate)
    {
        $orders = $this->repo->getOrdersByDateRange($startDate, $endDate);

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

Definiamo l'interfaccia che implementa un metodo output


namespace Mio\Plugin\Interfaces;

interface OrdersOutPutInterface {
    public function output($orders);
}

Creiamo la classe HtmlOutput che implementa l'interfaccia OrdersOutPutInterface appena creata



namespace Mio\Plugin\Formatters;

use Mio\Plugin\Interfaces\OrdersOutPutInterface;

class HtmlOutput implements OrdersOutPutInterface
{
    public function output($orders)
    {
        return '

Orders: ' . $orders . '

'; } }

Definiamo il repository OrdersRepository che implementa il metodo getOrdersByDateRange



namespace Mio\Plugin\Repositories;

class OrdersRepository {
    public function getOrdersByDateRange($startDate, $endDate)
    {
        return DB::table('orders')->whereBetween('created_at', [$startDate, $endDate])->get();
    }
}

Ridefiniamo anche la route in modo da passare alla nuova istanza di OrderReport le due classi del repository e del formatter


Route::get('/ordini', function(){
   $report = new \Mio\Plugin\Reports\OrdersReport(
        new Mio\Plugin\Repositories\OrdersRepository,
        new Mio\Plugin\Formatters\HtmlOutput
    );
   $startDate = Carbon\Carbon::now()->subDays(10);
   $endDate = Carbon\Carbon::now();

   return $report->getOrders($startDate, $endDate);
});

Come possiamo chiaramente vedere scrivere codice robusto e pulito non necessariamente vuol dire scriverne meno, anzi, siamo passati da una classe con tre metodi a tre classi e un'interfaccia però ora ognuna di esse ha singola responsabilità come richiesto dal primo principio SOLID.

Questo è quanto, nel prossimo articolo della serie vedremo il principio Open/Closed e cosa si intende con esso.

Happy coding!


Lunga vita e prosperità

Ti interessa un argomento non trattato?