Mi sono chiesto che cosa rende speciale Iterator rispetto ad altri costrutti simili, e questo ha reso la Gang of Four elencalo come schema di progettazione.
L'Iterator si basa sul polimorfismo (una gerarchia di raccolte con un'interfaccia comune) e sulla separazione delle preoccupazioni (l'iterazione sulle raccolte dovrebbe essere indipendente dal modo in cui i dati sono strutturati).
Ma cosa succede se sostituiamo la gerarchia delle collezioni con, ad esempio, una gerarchia di oggetti matematici (intero, float, complesso, matrice ecc.) e l'iteratore di una classe che rappresenta alcune operazioni correlate su questi oggetti, ad esempio il potere funzioni. Il diagramma di classe sarebbe lo stesso.
Probabilmente potremmo trovare molti altri esempi simili come Writer, Painter, Encoder e probabilmente quelli migliori, che funzionano allo stesso modo. Tuttavia, non ho mai sentito nessuno di questi essere chiamato Design Pattern.
Quindi cosa rende speciale Iterator?
È il fatto che è più complicato perché richiede uno stato mutabile per la memorizzazione della posizione corrente all'interno della raccolta? Ma poi, lo stato mutabile di solito non è considerato desiderabile.
Per chiarire il mio punto, vorrei fare un esempio più dettagliato.
Ecco il nostro problema di progettazione:
Diciamo che abbiamo una gerarchia di classi e un'operazione definita sugli oggetti di queste classi. L'interfaccia di questa operazione è la stessa per ogni classe, ma le implementazioni possono essere completamente diverse. Si presume inoltre che abbia senso applicare l'operazione più volte sullo stesso oggetto, ad esempio con parametri diversi.
Ecco una soluzione ragionevole per il nostro problema di progettazione (praticamente una generalizzazione del pattern iteratore):
Per la separazione delle preoccupazioni, le implementazioni dell'operazione non dovrebbero essere aggiunte come funzioni alla gerarchia di classi originale (oggetti operandi). Poiché desideriamo applicare l'operazione più volte sullo stesso operando, dovrebbe essere rappresentata da un oggetto che contiene un riferimento all'operando, non solo da una funzione. Pertanto l'oggetto operando dovrebbe fornire una funzione che restituisca l'oggetto che rappresenta l'operazione. Questo oggetto fornisce una funzione che esegue l'operazione effettiva.
Un esempio:
C'è una classe base o un'interfaccia MathObject
(nome stupido, lo so, forse qualcuno ha un'idea migliore.) con classi derivate MyInteger
e MyMatrix
. Per ogni MathObject
deve essere definita un'operazione Power
che consente il calcolo di quadrati, cubi e così via. Quindi potremmo scrivere (in Java):
MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125