Costruisce oggetti complessi usando un approccio step by step, per esempio creando altri oggetti minori che compongono l'oggetto principale e assegnandoli di volta in volta.
Questo pattern coinvolge una singola classe la quale è responsabile di creare un oggetto mentre si assicura che solo un singolo oggetto venga creato. In pratica questa classe non può essere istanziata, o meglio ogni istanza è in realtà l'accesso alla stessa univoca istanza.
Consiste nell'implementazione di un'interfaccia "prototype" che comunica la creazione di un clone dell'oggetto corrente. Simile al Factory
come concetto genera tuttavia dei cloni dell'oggetto da cui viene eseguita la creazione della nuova istanza invece di essere una istanza base con valori di default come nel Factory
.
Una classe riceve istruzione di generare e restituire altri tipi di classi in base a varie condizioni.
Es. Rectangle
, Square
e Circle
implementano Shape
. ShapeFactory
, in base alla stringa ricevuta nel metodo getShape()
, restituisce una nuova istanza di una di queste classi.
Es. classe MediaPlayer
ha play()
mentre AudioPlayer
ha playVLC()
e playMp3()
. Entrambe estendono un'interfaccia, i metodi li hanno entrambi ma non sono implementati. Creo una classe "adapter" con un metodo play()
che, in base al tipo, esegue il metodo corretto (tra playVlc()
e playMp3()
). E’ utilizzato come possibile soluzione di refactoring in caso il code smelling rilevi violazioni del quarto principio SOLID (Interface Segregation).
Consiste in una classe che si auto implementa in maniera gerarchica avendo una lista di istanze figlie della stessa classe.
Si crea una classe astratta nel cui costruttore viene fornita l'implementazione di un'interfaccia.
Es. RedCircle
e GreenSquare
implementano l'interfaccia DrawApi
e il metodo draw()
. La classe astratta Shape
ha un attributo drawApi
di tipo DrawApi
e il metodo astratto draw()
.
La classe Circle
estende Shape
implementando draw()
come drawApi.drawCircle()
e fornendo i suoi attributi specifici.
In questo modo posso fornire il tipo di estensione di Shape
come parametro del costrutture quando vado a creare una Shape
, es: Shape redCircle = new Circle(100,100,10, new RedCircle())
.
Avendo una classe raggruppabile in liste, creo delle classi che estendono l'interfaccia "filter" con un metodo di filtraggio. In questo metodo, riceveranno una lista di istanze della prima classe e ne restituiranno la stessa filtrata in base ai criteri implementati nelle varie implementazioni del filter.
Avendo due classi che implementano la stessa interfaccia, il decorator consiste in un'ulteriore oggetto il quale ha una delle primi classi come attributo e nei suoi metodi esegue chiamate a quegli stessi attributi permettendo, però, di eseguire operazioni prima e/o dopo. In pratica si crea un "wrapper" della funzione intorno ad una funzione.
Avendo diverse classi che implementano un'interfaccia, creo una classe "facade" la quale genera delle istanze di queste classi e ne "espone" dei metodi. Serve per semplificare l'utilizzo di gerarchie complesse di oggetti.
Avendo un oggetto, si crea una classe "factory" che conserva una mappa di istanze dell'oggetto utilizzando come chiave il valore assegnabile ad un attributo dell'oggetto. L'algoritmo richiede al factory un'istanza con un determinato attributo. Se già esistente la classe factory la fornisce prendendola dalla mappa e usando come chiave il valore dell'attributo stesso, altrimenti la crea e la inserisce nella mappa per poi restituirla.
La classe "proxy" consiste in una classe che "contiene" come attributo un'istanza di un'altra classe, decorandone i metodi. Serve a creare un'interfaccia tra lo scope esterno e la classe contenuta.
Utilizzato nel CacheService
del servizio Onboarding (Finance Evolution).
Questo pattern crea una catena di oggetti che processano una richiesta. Ognuna delle classi che implementa il pattern possiede un riferimento a quella successiva che prenderà in carico di eseguire l'operazione successiva della catena.
Delle classi implementano delle azioni. Altre classi possono quindi eseguire queste azioni creando delle operazioni più complesse che possono comprendere più azioni.
Fornisce un modo di valutare un'espressione ed eseguirla via codice.
Permette di scorrere un elemento iterabile. Ogni istanza che implementa l'iterator possiede un riferimento all'elemento successivo (come nel Chain of Responsability avviene con l'incaricato nella catena di esecuzione). In questo modo, partendo dal primo, si possono raggiungere tutti gli elementi da iterare finchè il metodo hasNext()
non restituisce nulla.
Riduce la complessità di comunicazione tra due o più oggetti.
Es. l'oggetto User
della chat ha un metodo sendMessage(String message)
il quale chiama il metodo statico showMessage()
dell'oggetto ChatRoom
consentendo di non dover chiamare, da parte dell'esecuzione principale, sia il send()
che lo show()
.
Notifica a tutti gli elementi in ascolto il cambiamento di un valore su un oggetto osservato dai vari listener.
Es. l'oggetto Subject
ha il metodo setState(int state)
dentro al quale viene chiamato il metodo notifyAllObservers()
. Quest’ultimo cicla gli Observer, memorizzati in una lista del Subject, e ne esegue il metodo update()
. Gli estensori dell'interfaccia Observer ricevono il Subject in fase di costruzione e si auto assegnano al subject di modo da averne coscienza dello stato, ma di consentirgli anche di eseguire sugli stessi l'update().
Tiene traccia degli stati di un oggetto per ripristinarli in caso di necessità.
Un oggetto Originator
cambia stato frequentemente. Ad ogni cambio di stato genera un oggetto Memento
che contiene lo stato. Un CareTaker
colleziona questi "mementi", quando salvati, e li restituisce in base alle richieste di ripristino.
In questo pattern una classe cambia il suo comportamento in base allo stato assegnato. Lo stato è un oggetto con dei metodi di esecuzione. Quando questi metodi vengono eseguiti viene passato come argomento un Context
il quale imposta come stato attuale l'oggetto-stato appena eseguito.
Dato un oggetto crea un'estensione dell'oggetto che rappresenti l'oggetto come null in modo da non dover creare troppe condizioni. Questo oggetto si comporterà come se fosse nullo ma senza interrompere il flusso di codice.
Prevede che una classe possa cambiare comportamento a runtime.
Es. diverse operazioni consistono in alcune classi che implementano l'interfaccia Strategy
con un metodo doOperation()
. Il Context
contiene un attributo di tipo Strategy
che può variare durante l'esecuzione assegnando implementazioni differenti di Strategy. Al momento della chiamata al metodo Context.executeStrategy()
viene eseguito il doOperation()
dello Strategy corrente.
Hint: state e strategy sono praticamente "complementari": nel primo viene impostato uno stato in seguito ad un'azione, nel secondo viene eseguita un'azione in seguito all'assegnazione di una strategia (anche se non necessariamente immediatamente conseguente nel secondo caso).
Una classe astratta espone dei template per eseguire i suoi metodi. Le sue sottoclassi possono overridare l'implementazione del metodo a necessità, ma l'invocazione deve essere la stessa della classe astratta.
Un oggetto di tipo "visitor" implementa diversi algoritmi di esecuzione ricevendo diversi oggetti come argomento overloadando il metodo visit()
. I vari oggetti possiedono un metodo accept()
nel quale ricevono il visitor che al suo interno esegue visit(this)
. In questo modo quando un oggetto esegue accept(istanza del visitor)
, l'algoritmo dello specifico visitor relativo allo specifico oggetto viene eseguito.