Cosa rende l'Iterator un modello di progettazione?


9

Mi chiedevo che cosa rende speciale Iterator rispetto ad altri costrutti simili, e che ha reso la Banda dei Quattro elencarlo come un modello di progettazione.

L'iteratore 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).

E se sostituiamo la gerarchia delle raccolte con, ad esempio, una gerarchia di oggetti matematici (intero, float, complesso, matrice ecc.) E l'iteratore da una classe che rappresenta alcune operazioni correlate su questi oggetti, ad esempio funzioni di potenza. Il diagramma di classe sarebbe lo stesso.

Probabilmente potremmo trovare molti altri esempi simili come Writer, Painter, Encoder e probabilmente altri migliori, che funzionano allo stesso modo. Tuttavia, non ho mai sentito nessuno di questi essere chiamato Design Pattern.

Cosa rende speciale Iterator?

È forse più complicato perché richiede uno stato mutabile per memorizzare la posizione corrente all'interno della raccolta? Ma poi, lo stato mutevole di solito non viene considerato desiderabile.


Per chiarire il mio punto, lasciami 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 anche 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 modello iteratore):

Per separare le preoccupazioni, le implementazioni dell'operazione non devono essere aggiunte come funzioni alla gerarchia di classi originale (oggetti operando). Dato che vogliamo applicare l'operazione più volte sullo stesso operando, dovrebbe essere rappresentato da un oggetto che contiene un riferimento all'operando, non solo da una funzione. Pertanto l'oggetto operando dovrebbe fornire una funzione che restituisce 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 MyIntegere MyMatrix. Per ciascuna dovrebbe essere definita MathObjectun'operazione Powerche consenta 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

5
"Il diagramma di classe sarebbe lo stesso" - e allora? Un modello di progettazione non è un diagramma di classe. È un'astrazione di alto livello per una classe di soluzioni a un problema ricorrente.
Doc Brown,

@DocBrown: Giusto, ma non sono operazioni matematiche, scrivere oggetti su un file, output grafico o codificare i problemi ricorrenti dei dati, proprio come l'iterazione?
Frank Puffer,

La scelta del Design Pattern è soggettiva (cioè agli occhi dei "designer" o delle persone che giudicano i design). La denominazione dei modelli di progettazione è intesa per essere indipendente dal dominio (in modo da non distrarci nel pensare che sia specifico del dominio). Solo la mia opinione, non ho alcun riferimento da citare.
rwong

@FrankPuffer Se si delinea una soluzione comune per la scrittura di oggetti in un file, è possibile annotare la soluzione e chiamarla modello di scrittura dell'oggetto se ciò è utile.
Brandin,

3
Stai pensando troppo a questo. Un modello di progettazione è una soluzione ben nota a un problema informatico comune, ed è tutto. Si utilizza il modello quando è possibile riconoscere e applicare i vantaggi offerti.
Robert Harvey,

Risposte:


9

La maggior parte degli schemi del libro GoF ha le seguenti cose in comune:

  • risolvono problemi di progettazione di base , usando mezzi orientati agli oggetti
  • le persone spesso affrontano questo tipo di problemi in programmi arbitrari, indipendentemente dal dominio o dall'azienda
  • sono ricette per rendere il codice più riutilizzabile, spesso rendendolo più SOLIDO
  • presentano soluzioni canoniche a questi problemi

I problemi risolti da questi schemi sono così basilari che molti sviluppatori li comprendono principalmente come soluzioni alternative per le funzioni del linguaggio di programmazione mancanti , che è un punto di vista valido per IMHO (si noti che il libro GoF è del 1995, dove Java e C ++ non offrivano così tanti caratteristiche come oggi).

Il modello iteratore si adatta bene a questa descrizione: risolve un problema di base che si verifica molto spesso, indipendentemente da qualsiasi dominio specifico, e come hai scritto da solo è un buon esempio di "separazione delle preoccupazioni". Come sicuramente saprai, il supporto dell'iteratore diretto è qualcosa che trovi oggi in molti linguaggi di programmazione disprezzo.

Ora confronta questo con i problemi che hai colto:

  • scrivere su un file - cioè IMHO semplicemente non "abbastanza" di base. È un problema molto specifico. Né esiste una buona soluzione canonica - ci sono molti approcci diversi su come scrivere su un file e nessuna chiara "best practice".
  • Pittore, Encoder: qualunque cosa tu abbia in mente, quei problemi mi sembrano ancora meno basilari, e nemmeno indipendenti dal dominio.
  • avere la funzione "power" disponibile per diversi tipi di oggetti: a prima vista, potrebbe valere la pena essere uno schema, ma la soluzione proposta non mi convince - sembra più un tentativo di innescare la funzione power in qualcosa di simile a il modello iteratore. Ho implementato molto codice con calcoli ingegneristici, ma non ricordo una situazione in cui un approccio simile al tuo oggetto funzione di potenza mi avrebbe aiutato (tuttavia, gli iteratori sono qualcosa che devo affrontare quotidianamente).

Inoltre, non vedo nulla nell'esempio della tua funzione di potere che non possa essere interpretato come un'applicazione del modello di strategia o del modello di comando, il che significa che quelle parti di base sono già nel libro GoF. Una soluzione migliore potrebbe contenere il sovraccarico dell'operatore o metodi di estensione, ma queste sono cose che sono soggette alle caratteristiche del linguaggio, ed è esattamente ciò che "OO significa" utilizzato da "Gang" non è in grado di fornire.


The problems solved by these patterns are so basic that many developers think their main purpose is to be workarounds for missing programming language features- L'ironia è che gli sviluppatori di software usano abitualmente modelli di progettazione del software che hanno 20 anni, pur credendo ancora di scrivere codice all'avanguardia.
Robert Harvey,

@RobertHarvey: Non credo che molti sviluppatori implementerebbero il modello iteratore oggi nel "modo OO" suggerito dal GoF. Lo implementano in genere con i mezzi forniti dalla lingua o dalla sua lib standard (ad esempio, in C # usando IEnumerablee yield). Ma per altri modelli GoF, ciò che hai scritto potrebbe essere probabilmente vero.
Doc Brown,

1
Rilevante per workarounds for missing programming language features: blog.plover.com/prog/johnson.html
jrw32982 supporta Monica il

8

La banda di quattro cita la definizione del modello di Christopher Alexander:

Ogni modello descrive un problema che si verifica ripetutamente nel nostro ambiente, e quindi descrive il nucleo della soluzione a quel problema [...]

Qual è il problema risolto dagli iteratori?

Intento: fornire un modo per accedere in sequenza agli elementi di un oggetto aggregato senza esporre la sua rappresentazione sottostante.

...

Applicabilità: utilizzare il modello Iterator

  • per accedere ai contenuti di un oggetto aggregato senza esporne la rappresentazione interna
  • per supportare più attraversamenti di oggetti aggregati
  • fornire un'interfaccia uniforme per attraversare diverse strutture aggregate (ovvero per supportare l'iterazione polimorfica).

Quindi si potrebbe sostenere che il modello iteratore è per definizione specifico del dominio per le raccolte. E questo è perfettamente OK. Altri modelli come il modello di interprete sono specifici del dominio a linguaggi specifici del dominio, i modelli di fabbrica sono specifici del dominio per la creazione di oggetti, ... Naturalmente questa è una comprensione piuttosto sciocca di "specifici del dominio". Finché si tratta di una coppia ricorrente di soluzione del problema, possiamo chiamarla modello.

Ed è positivo che esista il modello Iterator. Le cose brutte accadono se non la usi. Il mio anti-esempio preferito è Perl. Qui, ogni raccolta (array o hash) include lo stato iteratore come parte della raccolta. Perché è così male? Possiamo iterare facilmente su un hash con un ciclo while - ogni:

while (my ($key, $value) = each %$hash) {
  say "$key => $value";
}

Ma cosa succede se chiamiamo una funzione nel corpo del loop?

while (my ($key, $value) = each %$hash) {
  do_something_with($key, $value, $hash);
}

Questa funzione ora può fare praticamente tutto ciò che vuole, tranne:

  • aggiungere o eliminare voci hash, poiché altererebbero in modo imprevedibile l'ordine di iterazione (in C ++ - parlare, invaliderebbero l'iteratore).
  • iterare la stessa tabella hash senza fare una copia, poiché ciò consumerebbe lo stesso stato di iterazione. Ops.

Se la funzione chiamata deve usare l'iteratore, il comportamento del nostro loop diventa indefinito. Questo è un problema E il modello iteratore ha una soluzione: mettere tutto lo stato di iterazione in un oggetto separato creato per iterazione.

Sì, ovviamente il pattern iteratore è correlato ad altri pattern. Ad esempio, come viene istanziata l'iteratore? In Java, abbiamo un generico Iterable<T>e Iterator<T>un'interfaccia. Un iterabile concreto ArrayList<T>crea un particolare tipo di iteratore, mentre un HashSet<T>potrebbe fornire un tipo di iteratore completamente diverso. Questo mi ricorda molto il modello astratto di fabbrica, dove Iterable<T>è la fabbrica astratta e Iteratorè il prodotto.

Un iteratore polimorfico può anche essere interpretato come un esempio del modello di strategia. Ad esempio un albero potrebbe offrire diversi tipi di iteratori (pre-ordine, in-order, post-order, ...). Esternamente, tutti condividono un'interfaccia iteratrice e restituiscono elementi in una sequenza. Il codice client deve dipendere solo dall'interfaccia dell'iteratore, non da un particolare algoritmo di attraversamento dell'albero.

I modelli non esistono in isolamento, indipendentemente l'uno dall'altro. Alcuni schemi sono soluzioni diverse per lo stesso problema e alcuni schemi descrivono la stessa soluzione in contesti diversi. Alcuni schemi implicano un altro. Inoltre, lo spazio dei pattern non viene chiuso quando si gira l'ultima pagina del libro Design Patterns (vedi anche la tua domanda precedente La banda di quattro ha esplorato a fondo lo "Spazio dei pattern"? ). I motivi descritti nel libro Modelli di disegno sono molto flessibili e ampi, aperti a infinite variazioni e in definitiva non gli unici motivi esistenti.

I concetti che elenchi (scrivere, dipingere, codificare) non sono schemi perché non descrivono una combinazione problema-soluzione. Un compito come "Ho bisogno di scrivere dati" o "Ho bisogno di codificare i dati" non è in realtà un problema di progettazione e non include una soluzione; una "soluzione" che consiste semplicemente in "So, creerò una classe Writer" non ha senso. Ma se abbiamo un problema come "Non voglio che la grafica con metà rendering sia dipinta sullo schermo", allora potrebbe esistere un modello: "Lo so, userò la grafica a doppio buffer!"


Bella risposta, grazie. Non sono ancora del tutto convinto che ciò che scrivi nell'ultimo paragrafo si applichi qui. Ho modificato la mia domanda per spiegare cosa intendo.
Frank Puffer,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.