Ci sono molti articoli che spiegano quali sono gli schemi di progettazione e come implementarli; il web non ha ancora bisogno di un altro di quegli articoli! Invece, in questo articolo, discuteremo di più su quando e perché, piuttosto che il quale e Come.
Presenterò diverse situazioni e casi d'uso per i modelli e fornirò anche brevi definizioni per aiutare coloro che non hanno familiarità con questi modelli specifici. Iniziamo.
Tutorial ripubblicatoOgni poche settimane, rivisitiamo alcuni dei post preferiti del nostro lettore da tutta la cronologia del sito. Questo tutorial è stato pubblicato per la prima volta nell'ottobre 2012.
Questo articolo copre alcuni dei vari Modelli di design agili, documentato nei libri di Robert C. Martin. Questi modelli sono adattamenti moderni dei modelli di progettazione originali definiti e documentati da La banda dei quattro nel 1994. I modelli di Martin presentano una versione molto più recente degli schemi del GoF e funzionano meglio con le tecniche e i problemi di programmazione moderni. In effetti, circa il 15% dei modelli originali è stato sostituito con modelli più recenti e gli schemi rimanenti sono stati leggermente modernizzati.
Il modello factory è stato inventato per aiutare i programmatori a organizzare le informazioni relative alla creazione di oggetti. Gli oggetti a volte hanno molti parametri di costruzione; altre volte, devono essere compilati con le informazioni predefinite immediatamente dopo la loro creazione. Questi oggetti dovrebbero essere creati nelle fabbriche, mantenendo tutte le informazioni relative alla loro creazione e all'inizializzazione contenute in un singolo luogo.
quando Usa uno schema di fabbrica quando ti trovi a scrivere codice per raccogliere le informazioni necessarie per creare oggetti.
Perché: Le fabbriche aiutano a contenere la logica della creazione di oggetti in un unico luogo. Possono anche rompere le dipendenze per facilitare l'accoppiamento libero e l'iniezione di dipendenza per consentire migliori test.
Esistono due schemi utilizzati di frequente per recuperare informazioni da un livello di persistenza o da un'origine dati esterna.
Questo schema definisce un canale di comunicazione tra una soluzione di persistenza e la logica aziendale. Per applicazioni più semplici, può recuperare o ricreare interi oggetti da solo, ma la creazione di oggetti è responsabilità delle fabbriche nelle applicazioni più complesse. I gateway semplicemente recuperano e persistono i dati grezzi.
Quando: Quando è necessario recuperare o persistere informazioni.
Perché: Offre una semplice interfaccia pubblica per complicate operazioni di persistenza. Incapsula anche la conoscenza della persistenza e disaccoppia la logica aziendale dalla logica di persistenza.
In effetti, il pattern gateway è solo una particolare implementazione di un altro pattern di progettazione di cui parleremo a breve: il pattern dell'adattatore.
Ci sono momenti in cui non puoi (o non vuoi) esporre la conoscenza del livello di persistenza alle tue classi di business. Il pattern proxy è un buon modo per ingannare le tue classi di business pensando che stiano usando oggetti già esistenti.
Quando: Devi recuperare informazioni da un livello di persistenza o da una fonte esterna, ma non vuoi che la tua logica aziendale sappia questo.
Perché: Offrire un approccio non intrusivo alla creazione di oggetti dietro le quinte. Apre anche la possibilità di recuperare questi oggetti al volo, pigramente e da fonti diverse.
Un proxy implementa efficacemente la stessa interfaccia di un oggetto reale e ne imita la funzionalità. La logica aziendale la usa semplicemente come se fosse un oggetto reale, ma in effetti il proxy crea l'oggetto se non esiste.
Anche il pattern degli oggetti attivi ha avuto un ruolo nei primi sistemi multi-tasking.
Ok ok. Che bello e tutto, ma come possiamo trovare gli oggetti che dobbiamo creare?
Il modello di repository è molto utile per l'implementazione di metodi di ricerca e linguaggi di mini-query. Richiede queste query e utilizza un gateway per ottenere i dati per una fabbrica per produrre gli oggetti necessari.
Il modello di repository è diverso dagli altri pattern; esiste come parte del Domain Driven Design (DDD) e non è incluso nel libro di Robert C. Martin.
Quando: È necessario creare più oggetti in base ai criteri di ricerca o quando è necessario salvare più oggetti sul livello di persistenza.
Perché: Per consentire ai clienti che necessitano di oggetti specifici di lavorare con una query e un linguaggio di persistenza comuni e ben isolati. Rimuove anche più codice correlato alla creazione dalla logica aziendale.
Ma cosa succede se il repository non riesce a trovare gli oggetti? Un'opzione sarebbe quella di restituire a NULLO
valore, ma così facendo ha due effetti collaterali:
se (is_null ($ param)) restituisce;
) nel tuo codice.Un approccio migliore è quello di restituire a nullo
oggetto.
Un oggetto nullo implementa la stessa interfaccia degli altri oggetti, ma i membri dell'oggetto restituiscono un valore neutro. Ad esempio, un metodo che restituisce una stringa restituirebbe una stringa vuota; un altro membro che restituisce un valore numerico restituirebbe zero. Questo ti costringe ad implementare metodi che non restituiscono dati significativi, ma puoi usare questi oggetti senza preoccuparti di lasciare il lascito o sporcare il tuo codice con assegni nulli.
Quando: Controlla frequentemente nullo
o hai rifiutato lasciti.
Perché: Può aggiungere chiarezza al tuo codice e ti costringe a pensare di più sul comportamento dei tuoi oggetti.
Non è insolito chiamare molti metodi su un oggetto prima che possa fare il suo lavoro. Ci sono situazioni in cui devi preparare un oggetto dopo la sua creazione prima di poterlo veramente usare. Questo porta alla duplicazione del codice quando si creano quegli oggetti in luoghi diversi.
Quando: Quando devi eseguire molte operazioni per preparare gli oggetti per l'uso.
Perché: Per spostare la complessità dal codice che consuma al codice di creazione.
Suona bene, vero? In effetti, è abbastanza utile in molte situazioni. Lo schema di comando è ampiamente utilizzato per implementare le transazioni. Se aggiungi un semplice disfare()
metodo a un oggetto comando, può tracciare tutte le transazioni di annullamento eseguite e invertirle se necessario.
Quindi ora hai dieci (o più) oggetti comando e li vuoi che vengano eseguiti contemporaneamente. Puoi raggrupparli in un oggetto attivo.
L'oggetto attivo semplice e interessante ha una sola responsabilità: mantenere un elenco di oggetti comando ed eseguirli.
Quando: Diversi oggetti simili devono essere eseguiti con un singolo comando.
Perché: Costringe i client a eseguire una singola attività e a influenzare più oggetti.
Un oggetto attivo rimuove ogni comando dal suo elenco dopo l'esecuzione del comando; nel senso, è possibile eseguire il comando solo una volta. Alcuni esempi reali di un oggetto attivo sono:
I modelli di progettazione sono qui per risolvere i problemi.
acquistare()
comando su ogni prodotto li rimuove dal carrello.Anche il pattern degli oggetti attivi ha avuto un ruolo nei primi sistemi multi-tasking. Ogni oggetto all'interno di un oggetto attivo manterrebbe un riferimento all'oggetto attivo. Eseguivano una parte dei loro lavori e poi si rimettevano in fila. Anche nei sistemi odierni, è possibile utilizzare un oggetto attivo per consentire ad altri oggetti di funzionare mentre si attende una risposta da un'altra applicazione.
Sono certo che tu abbia sentito la grande promessa della programmazione orientata agli oggetti: il riutilizzo del codice. I primi utilizzatori di OOP prevedevano l'uso di librerie e classi universali in milioni di progetti diversi. Beh, non è mai successo.
Questo modello consente il parziale riutilizzo del codice. È pratico con più algoritmi che differiscono leggermente l'uno dall'altro.
Quando: Elimina la duplicazione in un modo semplice.
Perché: C'è duplicazione e flessibilità non è un problema.
Ma la flessibilità è bella. Cosa succede se ne ho davvero bisogno?
Quando: Flessibilità e riusabilità sono più importanti della semplicità.
Perché: Usalo per implementare grandi blocchi intercambiabili di logica complicata, mantenendo al contempo una firma di algoritmo comune.
Ad esempio, puoi creare un generico Calcolatrice
e quindi usare diversi ComputationStrategy
oggetti per eseguire i calcoli. Questo è un modello moderatamente usato ed è molto potente quando devi definire molti comportamenti condizionali.
Man mano che i progetti crescono, diventa sempre più difficile per gli utenti esterni accedere alla nostra applicazione. Questo è uno dei motivi per offrire un punto di accesso ben definito all'applicazione o al modulo in questione. Altri motivi possono includere il desiderio di nascondere i meccanismi e la struttura interna del modulo.
Una facciata è essenzialmente un'API: un'interfaccia piacevole e rivolta al cliente. Quando un cliente chiama uno di questi metodi, la facciata delega una serie di chiamate alle classi che nasconde per fornire al cliente le informazioni richieste o il risultato desiderato.
Quando: Semplificare la tua API o nascondere intenzionalmente la logica aziendale interna.
Perché: È possibile controllare l'API e le reali implementazioni e la logica in modo indipendente.
Il controllo è buono e molte volte è necessario eseguire un'attività quando qualcosa cambia. Gli utenti devono essere avvisati, i LED rossi devono lampeggiare, deve suonare un allarme ... si ottiene l'idea.
Il popolare framework Laravel fa un uso eccellente del modello di facciata.
Un oggetto nullo implementa la stessa interfaccia degli altri oggetti.
Il modello di osservatore offre un modo semplice per monitorare oggetti e intraprendere azioni quando le condizioni cambiano. Esistono due tipi di implementazioni di osservatori:
Quando: Fornire un sistema di notifica all'interno della propria logica aziendale o al mondo esterno.
Perché: Il modello offre un modo per comunicare eventi a qualsiasi numero di oggetti diversi.
I casi d'uso per questo modello sono le notifiche e-mail, i daemon di registrazione oi sistemi di messaggistica. Certo, nella vita reale ci sono innumerevoli altri modi per usarlo.
Lo schema dell'osservatore può essere esteso con un modello di mediatore. Questo modello prende due oggetti come parametri. Il mediatore si iscrive al primo parametro e quando un cambiamento avviene sull'oggetto osservato, il mediatore decide cosa fare sul secondo oggetto.
Quando: Gli oggetti interessati non possono sapere degli oggetti osservati.
Perché: Offrire un meccanismo nascosto di influenza su altri oggetti nel sistema quando un oggetto cambia.
A volte, hai bisogno di oggetti speciali che sono unici nella tua applicazione e vuoi assicurarti che tutti i consumatori possano vedere qualsiasi modifica apportata a questi oggetti. Inoltre, si desidera impedire la creazione di più istanze di tali oggetti per determinati motivi, ad esempio lunghi tempi di inizializzazione o problemi con azioni concorrenti ad alcune librerie di terze parti.
Un singleton è un oggetto che ha un costruttore privato e un pubblico getInstance ()
metodo. Questo metodo garantisce che esiste solo un'istanza dell'oggetto.
Quando: È necessario raggiungere la singolarità e desiderare una piattaforma incrociata, una soluzione ponderata che offre anche la possibilità di creare attraverso la derivazione.
Perché: Offrire un singolo punto di accesso quando necessario.
Un altro approccio alla singolarità è il modello di progettazione monostatico. Questa soluzione utilizza un trucco offerto da linguaggi di programmazione orientati agli oggetti. Dispone di metodi pubblici dinamici che ottengono o impostano i valori delle variabili private statiche. Ciò, a sua volta, assicura che tutte le istanze di tali classi condividano gli stessi valori.
Quando: Trasparenza, derivabitilità e polimorfismo sono preferiti insieme alla singolarità.
Perché: Per nascondere agli utenti / clienti il fatto che l'oggetto offra una singolarità.
Presta particolare attenzione alla singolarità. Inquina lo spazio dei nomi globale e, nella maggior parte dei casi, può essere sostituito con qualcosa di più adatto a quella particolare situazione.
Il modello di repository è abbastanza utile per l'implementazione dei metodi di ricerca ...
Quindi hai un interruttore e una luce. L'interruttore può accendere e spegnere la luce, ma, ora, hai acquistato un ventilatore e vuoi usare il tuo vecchio interruttore con esso. È facile da realizzare nel mondo fisico; prendi l'interruttore, collega i fili e viola.
Sfortunatamente, non è così facile nel mondo della programmazione. Hai un Interruttore
classe e a Luce
classe. Se tuo Interruttore
usa il Luce
, come potrebbe usare il Fan
?
Facile! Copia e incolla il Interruttore
, e cambialo per usare il Fan
. Ma questa è duplicazione del codice; è l'equivalente di acquistare un altro interruttore per la ventola. Potresti estendere Interruttore
a FanSwitch
, e usa invece quell'oggetto. Ma cosa succede se si desidera utilizzare un Pulsante
o RemoteControl
, invece di a Interruttore
?
Questo è il modello più semplice mai inventato. Usa solo un'interfaccia. Questo è tutto, ma ci sono diverse implementazioni.
Quando: Devi connettere oggetti e mantenere la flessibilità.
Perché: Perché è il modo più semplice per raggiungere la flessibilità, rispettando sia il principio di inversione delle dipendenze sia il principio di open close.
PHP è digitato in modo dinamico. Ciò significa che è possibile omettere le interfacce e utilizzare oggetti diversi nello stesso contesto, rischiando un lascito rifiutato. Tuttavia, PHP consente anche la definizione di interfacce e ti consiglio di utilizzare questa grande funzionalità per fornire chiarezza nell'intento del tuo codice sorgente.
Ma hai già un sacco di classi con cui vuoi parlare? Sì, naturalmente. Ci sono molte librerie, API di terze parti e altri moduli con cui si deve parlare, ma ciò non significa che la nostra logica aziendale debba conoscere i dettagli di tali cose.
Il modello dell'adattatore crea semplicemente una corrispondenza tra la logica aziendale e qualcos'altro. Abbiamo già visto un simile modello in azione: il modello di gateway.
Quando: È necessario creare una connessione con un modulo, una libreria o un'API preesistente e potenzialmente modificabile.
Perché: Per consentire alla logica aziendale di fare affidamento solo sui metodi pubblici offerti dall'adattatore e consentire di modificare facilmente l'altro lato dell'adattatore.
Se uno dei modelli sopra indicati non si adatta alla tua situazione, potresti usare ...
Questo è un modello molto complicato. Personalmente non mi piace perché di solito è più semplice adottare un approccio diverso. Ma per quei casi speciali, quando altre soluzioni falliscono, puoi considerare il modello del ponte.
Quando: Il modello dell'adattatore non è sufficiente e si modificano le classi su entrambi i lati del tubo.
Perché: Offrire una maggiore flessibilità al costo di una complessità significativa.
Considera di avere uno script con comandi simili e vuoi effettuare una singola chiamata per eseguirli. Aspettare! Non abbiamo già visto qualcosa di simile prima? Il modello di oggetto attivo?
Sì, sì, l'abbiamo fatto. Ma questo è un po 'diverso. È il modello composito e, come il modello di oggetto attivo, mantiene un elenco di oggetti. Ma chiamare un metodo su un oggetto composito chiama lo stesso metodo su tutti i suoi oggetti senza rimuoverli dall'elenco. I client che chiamano un metodo pensano di parlare a un singolo oggetto di quel particolare tipo, ma in realtà le loro azioni sono applicate a molti, molti oggetti dello stesso tipo.
Quando: Devi applicare un'azione a diversi oggetti simili.
Perché: Ridurre la duplicazione e semplificare il modo in cui vengono chiamati oggetti simili.
Ecco un esempio: hai un'applicazione che è in grado di creare e posizionare Ordini
. Supponiamo di avere tre ordini: $ order1
, $ order2
e $ order3
. Potresti chiamare posto()
su ognuno di essi, oppure potresti contenere quegli ordini in a $ compositeOrder
oggetto e chiamatelo posto()
metodo. Questo, a sua volta, chiama il posto()
metodo su tutto il contenuto Ordine
oggetti.
I gateway recuperano e persistono solo i dati non elaborati.
Una macchina a stati finiti (FSM) è un modello che ha un numero finito di stati discreti. L'implementazione di un FSM può essere difficile e il modo più semplice per farlo coinvolge il fidato interruttore
dichiarazione. Ogni Astuccio
l'istruzione rappresenta uno stato corrente nella macchina e sa come attivare lo stato successivo.
Ma lo sappiamo tutti scatola dell'interruttore
le affermazioni sono meno desiderabili perché producono un fan-out indesiderato sui nostri oggetti. Quindi dimentica il scatola dell'interruttore
dichiarazione, e invece considerano il modello di stato. Lo schema di stato è composto da diversi oggetti: un oggetto per coordinare le cose, un'interfaccia che rappresenta uno stato astratto e quindi diverse implementazioni - una per ogni stato. Ogni stato sa quale stato viene dopo di esso e lo stato può notificare all'oggetto coordinatore di impostare il suo nuovo stato sul successivo in linea.
Quando: È richiesta la logica simile a FSM.
Perché: Per eliminare i problemi di a scatola dell'interruttore
dichiarazione, e per meglio incapsulare il significato di ogni singolo stato.
Un distributore di cibo potrebbe avere un principale
classe che ha un riferimento a a stato
classe. Le possibili classi di stato potrebbero essere qualcosa come: WaitingForCoin
, InsertedCoin
, SelectedProduct
, WaitingForConfirmation
, DeliveringProduct
, ReturningChange
. Ogni stato svolge il proprio lavoro e crea l'oggetto stato successivo da inviare alla classe coordinatore.
Ci sono momenti in cui si distribuiscono classi o moduli in un'applicazione e non è possibile modificarli senza influire in modo radicale sul sistema. Ma, allo stesso tempo, è necessario aggiungere nuove funzionalità richieste dagli utenti.
Il modello di decoratore può aiutare in queste situazioni. È molto semplice: prendi le funzionalità esistenti e aggiungile. Ciò si ottiene estendendo la classe originale e fornendo nuove funzionalità in fase di esecuzione. I vecchi client continuano a utilizzare il nuovo oggetto come se fossero vecchi e i nuovi client utilizzeranno sia la vecchia che la nuova funzionalità.
Quando: Non è possibile modificare le vecchie classi, ma è necessario implementare un nuovo comportamento o stato.
Perché: Offre un modo non invadente per aggiungere nuove funzionalità.
Un semplice esempio è la stampa di dati. Si stampano alcune informazioni all'utente come testo normale, ma si desidera fornire anche la possibilità di stampare in HTML. Il modello decoratore è una soluzione che ti consente di mantenere entrambe le funzionalità.
Se il problema dell'estensione delle funzionalità è diverso, ad esempio, si ha una struttura ad albero complessa di oggetti e si desidera aggiungere funzionalità a più nodi contemporaneamente: una semplice iterazione non è possibile, ma un visitatore potrebbe essere una soluzione praticabile. Lo svantaggio, tuttavia, è che l'implementazione di un modello di visitatore richiede modifiche alla vecchia classe se non è stata progettata per accettare un visitatore.
Quando: Un decoratore non è appropriato e alcune complessità extra sono accettabili.
Perché: Consentire un approccio organizzato alla definizione di funzionalità per diversi oggetti ma al prezzo di una maggiore complessità.
Usa schemi di progettazione per risolvere i tuoi problemi, ma solo se si adattano.
Gli schemi di progettazione aiutano a risolvere i problemi. Come raccomandazione di implementazione, non nominare mai le classi dopo i pattern. Invece, trova i nomi giusti per le astrazioni giuste. Questo ti aiuta a discernere meglio quando hai davvero bisogno di un modello piuttosto che implementarlo solo perché è possibile.
Alcuni potrebbero dire che se non si assegna un nome alla classe con il nome del modello, allora gli altri sviluppatori avranno difficoltà a capire il codice. Se è difficile riconoscere un modello, il problema è nell'implementazione del modello.
Usa schemi di progettazione per risolvere i tuoi problemi, ma solo se si adattano. Non abusare di loro. Scoprirai che una soluzione più semplice si addice a un piccolo problema; mentre scoprirai che hai bisogno di un modello solo dopo aver implementato alcune altre soluzioni.
Se sei nuovo nel design dei pattern, spero che questo articolo ti abbia dato un'idea di come i pattern possono essere utili nelle tue applicazioni. Grazie per aver letto!