Pattern creazionali
Factory method
Intent
Fornisce un’interfaccia per la creazione di oggetti in una superclasse, ma permette alle sottoclassi di modificare il tipo di oggetti che verranno creati.
Problema
Supponiamo infatti di voler fare un’app per la logistica. Inizialmente ci occupiamo solo del trasporto via camion, quindi la maggior parte del nostro codice vive all’interno della classe Truck. A un certo punto decidiamo di espanderci, occupandoci anche della logistica via navi. Rischieremmo perciò di dover ricreare tutto da capo, e questo accadrebbe per ogni tipo di trasporto che sia diverso dai camion.
Soluzione
Il Factory Method suggerisce perciò di mantenere astratto l’oggetto che vogliamo creare in una superclasse, e poi useremo le sottoclassi per specificare l’oggetto specifico. Per cui sia camion, nave, aereo, ecc... riprenderanno un'unica interfaccia che avrà metodi astratti, le sottoclassi implementeranno questi metodi.
Dove si usa questo pattern?
- Quando non si conoscono in anticipo i tipi esatti e le dipendenze dagli oggetti con cui il nostro codice dovrebbe operare.
- Quando vogliamo fornire agli utenti della nostra libreria o del nostro framework un modo per estendere i suoi componenti interni.
- Quando si desidera risparmiare risorse di sistema riutilizzando gli oggetti esistenti invece di ricostruirli ogni volta.
Pro:
- Si evita l’accoppiamento stretto tra il Creator e i Concrete Product.
- Si facilita il principio della responsabilità unica, ovvero tutta la creazione di un prodotto si trova in un unico punto del programma che è la classe Creator.
- Principio aperto/chiuso, ovvero si introducono nuovi prodotti senza rovinare il codice esistente.
Contro:
- Il codice può diventare più complicato perché è necessario introdurre molte nuove sottoclassi.
Abstract method
Intent
Permette di produrre famiglie di oggetti correlati senza specificare le loro classi concrete.
Problema
Supponiamo di creare un simulatore di negozio di mobili, quindi il nostro codice è costituito da classi che rappresentano, ad esempio, sedia, divano e tavolo. Di ogni oggetto sono disponibili diversi stili per ogni famiglia di oggetti, come ad esempio moderno, vittoriano e decò. Per cui è necessario un modo per creare singoli oggetti d’arredo in modo che si abbinino ad altri oggetti della stessa famiglia, nel senso che i mobili devono essere dello stesso stile fra di loro. Inoltre si vuole che quando si aggiungono nuovi prodotti al programma, il codice non venga stravolto.
Soluzione
Innanzitutto si crea per ogni famiglia di prodotti un’interfaccia (come ad esempio sedia). Le relative sottoclassi saranno perciò SediaModerna, SediaVittoriana, ecc... Inoltre si crea un’interfaccia con un elenco di metodi di creazione per tutti i prodotti che fanno parte della famiglia. Successivamente si deve fare in modo che se un Client ha ordinato una sedia moderna e poi deve ordinare un tavolo, anche questo deve essere moderno.
Dove si usa questo pattern?
- Viene usato quando il nostro codice deve lavorare con varie famiglie di prodotti correlati, ma non vogliamo che dipenda dalle classi concrete di questi prodotti, che potrebbero essere sconosciute in anticipo o semplicemente vogliamo permettere una futura estensibilità.
Pro:
- Si evita l’accoppiamento stretto tra i prodotti concreti e il codice client.
- Si facilita il principio della responsabilità unica, ovvero tutta la creazione di un prodotto si trova in un unico punto del programma.
- Principio aperto/chiuso, ovvero si introducono nuovi prodotti senza rovinare il codice esistente.
Contro:
- Il codice può diventare più complicato perché è necessario introdurre molte nuove interfacce e sottoclassi che ereditano da esse.
Builder
Intent
Permette di costruire oggetti complessi passo dopo passo. Il pattern consente di produrre diversi tipi e rappresentazioni di un oggetto utilizzando lo stesso codice per la loro costruzione.
Problema
Immaginiamo un oggetto complesso che richiede un’inizializzazione laboriosa e graduale di molti campi, come ad esempio una casa. Il minimo per poter costruire una casa sono le fondamenta, le pareti, delle finestre, le porte e un tetto. A questi requisiti minimi si aggiungono degli optional che possono essere più o meno elaboriosi, e ci possono essere o non essere presenti.
Soluzione
Il pattern Builder suggerisce di estrarre il codice di costruzione dell’oggetto dalla propria classe e di spostarlo in oggetti separati chiamati builder. Per cui per creare un oggetto, si esegue una serie di questi passi su un oggetto builder. La parte importante è che non è necessario chiamare tutti i passi. Si possono chiamare solo quei passi che sono necessari per produrre una particolare configurazione di un oggetto. È possibile creare diverse classi builder che implementano la stessa serie di fasi di costruzione, ma in modo diverso (ad es. un builder costruisce tutto in legno, un altro in solo cemento, ecc...).
Dove si usa questo pattern?
- Si usa per eliminare un costruttore estremamente lungo.
- Quando vogliamo che il nostro codice sia in grado di creare diverse rappresentazioni di alcuni prodotti.
- Per costruire alberi compositi o altri oggetti complessi.
Pro:
- È possibile costruire gli oggetti passo dopo passo.
- È possibile riutilizzare lo stesso codice di costruzione quando si costruiscono diverse rappresentazioni di prodotti.
- Si facilita il principio della responsabilità unica, ovvero tutta la creazione di un prodotto si trova in un unico punto del programma.
Contro:
- Il codice può diventare più complicato perché il pattern richiede la creazione di più classi nuove.
Prototype
Intent
Permette di copiare oggetti esistenti senza rendere il codice dipendente dalle loro classi.
Problema
Supponiamo di avere un oggetto e di voler creare una copia esatta. Il problema è che quando copiamo un oggetto, una parte di esso è privato. E quindi rischio di copiare l’oggetto in modo incompleto.
Soluzione
Il pattern dichiara un’interfaccia comune per tutti gli oggetti che supportano la clonazione. Questa interfaccia contiene solo il singolo metodo Clone. Questo metodo crea un oggetto della classe e riesce a portare i valori dei campi del vecchio oggetto nel nuovo oggetto (anche i campi privati, questo perché posso accedere ai campi privati di altri oggetti che appartengono alla stessa classe). Un oggetto che supporta la clonazione è chiamato prototype. (Possiamo vedere la cosa come una sorta di analogia con la mitosi cellulare).
Dove si usa questo pattern?
- Quando il nostro codice non dovrebbe dipendere dalle classi concrete di oggetti che dobbiamo copiare.
- Quando vogliamo ridurre il numero di sottoclassi che si differenziano solo per il modo in cui inizializzano i rispettivi oggetti.
Pro:
- È possibile clonare oggetti senza accoppiarli alle loro classi concrete.
- È possibile eliminare il codice di inizializzazione ripetuto a favore della clonazione di prototipi pre-costruiti.
- È possibile produrre oggetti complessi in modo più conveniente.
- Si ottiene un’alternativa all’ereditarietà.
Contro:
- La clonazione di oggetti complessi che hanno riferimenti circolari potrebbe essere molto complicata.
Singleton
Intent
Consente di garantire che una classe abbia una sola istanza, fornendo al contempo un punto di accesso globale a questa istanza.
Problema
Supponiamo di voler fare in modo che di una classe ci sia un singolo oggetto e un singolo punto di accesso globale a quell’istanza, per cui abbiamo due problemi, violando così il principio della responsabilità unica. Questo viene fatto per garantire la mutua esclusione, risolvendo così il problema della concorrenza.
Soluzione
C’è una classe Singleton che ha una sola istanza con un attributo privato di tipo Singleton, un metodo privato, e un metodo pubblico che mi restituisce l’istanza.
Dove si usa questo pattern?
- Quando una classe del nostro programma dovrebbe avere solo una singola istanza disponibile per tutti i client.
- Quando è necessario un controllo più rigoroso sulle variabili globali.
Pro:
- Abbiamo la garanzia di avere un accesso unico senza l’utilizzo di variabili statiche.
Contro:
- Questo pattern viola il principio della responsabilità unica.
- Il codice diventa più complicato.
- Se si lavora con i multithread le cose si complicano.
- È difficile da testare.
Pattern strutturali
Adapter
Intent
Consente la collaborazione di oggetti con interfacce incompatibili.
Problema
Supponiamo di avere un’applicazione che fa delle analisi sul mercato azionario. Questa però scarica i dati unicamente in formato XML, mentre le librerie di Python richiedono il formato JSON. C’è un’incompatibilità, per cui serve una specie di adattatore.
Soluzione
Il pattern Adapter crea questo adattatore. Questo adattatore maschera uno degli oggetti per nascondere la complessità della conversione che avviene dietro le quinte. L’oggetto mascherato non è consapevole dell’adattatore. L’adattatore definisce un’interfaccia compatibile con uno degli oggetti esistenti. Utilizzando questa interfaccia, l’oggetto esistente può chiamare in modo sicuro i metodi dell’adattatore.
Dove si usa questo pattern?
- Quando vogliamo usare una classe esistente, ma la sua interfaccia non è compatibile con il resto del nostro codice.
- Quando desideriamo riutilizzare diverse sottoclassi.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.