modello "Collection Wrapper" - è comune?

1

Una mia domanda diversa ha a che fare con l'incapsulamento delle strutture di dati dei membri all'interno delle classi. Per comprendere meglio questa domanda, leggi questa domanda e osserva l'approccio discusso.

Uno dei ragazzi che ha risposto a quella domanda ha detto che l'approccio è buono, ma se l'ho capito correttamente - ha detto che dovrebbe esserci una classe esistente allo scopo di avvolgere la collezione, invece di una classe ordinaria che offre una numero di metodi pubblici solo per accedere alla collezione dei membri.

Ad esempio, invece di questo:

class SomeClass{

    // downright exposing the concrete collection.        

    Things[] someCollection;

   // other stuff omitted

    Thing[] getCollection(){return someCollection;}

}

O questo:

class SomeClass{

    // encapsulating the collection, but inflating the class' public interface.

    Thing[] someCollection;

    // class functionality omitted.

    public Thing getThing(int index){
        return someCollection[index];
    }

    public int getSize(){
        return someCollection.length;
    }

    public void setThing(int index, Thing thing){
        someCollection[index] = thing;
    }

    public void removeThing(int index){
        someCollection[index] = null;
    }

}

Avremo questo:

// encapsulating the collection - in a different class, dedicated to this.

class SomeClass{
    CollectionWrapper someCollection;

    CollectionWrapper  getCollection(){return someCollection;}
}

class CollectionWrapper{

    Thing[] someCollection;

    public Thing getThing(int index){
        return someCollection[index];
    }

    public int getSize(){
        return someCollection.length;
    }

    public void setThing(int index, Thing thing){
        someCollection[index] = thing;
    }

    public void removeThing(int index){
        someCollection[index] = null;
    }

}

In questo modo, la struttura interna dei dati in SomeClass può cambiare senza influenzare il codice cliente e senza forzare SomeClass per offrire molti metodi pubblici solo per accedere alla raccolta interna. CollectionWrapper invece fa.

es. se la raccolta passa da una matrice a una List , l'implementazione interna di CollectionWrapper cambia, ma il codice client rimane lo stesso.

Inoltre, CollectionWrapper può nascondere alcune cose dal codice client - ad esempio, può non consentire la mutazione alla raccolta non avendo i metodi setThing e removeThing .

Questo approccio al disaccoppiamento del codice client dalla struttura dati concreta sembra IMHO piuttosto buono.

Questo approccio è comune? Quali sono le cadute? È usato in pratica?

    
posta Aviv Cohn 28.05.2014 - 18:45
fonte

4 risposte

6

Stai implementando un set limitato dell'interfaccia Raccolta , senza dargli effettivamente qualcosa con cui puoi lavorare.

E.g. if the collection changes from an array to a List, the internal implementation of CollectionWrapper changes, but client code stays the same.

Questo è esattamente ciò che l'interfaccia Collection è destinata a fare. Se passi indietro una raccolta, da cui tutto il client può lavorare.

Also, the CollectionWrapper can hide certain things from the client code - from example, it can disallow mutation to the collection by not having the methods setThing and removeThing.

In Java, questo è noto come Collezione non modificabile e Il programma di utilità per le raccolte ha proprio questo metodo per convertire una raccolta modificabile in una non modificabile.

Creando il wrapper limitato si rinuncia anche a cose come ordina e iteratore che viene in molto a portata di mano di tanto in tanto. Questo significa codice come:

for(Thing t : someCollection) {
    ....
}

non funzionerà. Invece, il codice dovrebbe essere:

for(int i = 0; i < someCollection.size(); i++) {
    Thing t = someCollection.get(i);
    ....
}

Sebbene sia un codice perfettamente accettabile, significa anche che non riesci a sfruttare il linguaggio per consentire al client di scrivere codice più velocemente (il tempo del programmatore è costoso).

Il modo in cui si disaccoppia l'implementazione consiste nell'utilizzare un'interfaccia, preferibilmente una che già esiste nel framework del linguaggio, in modo che altre cose che utilizzano quella stessa interfaccia possano usare il tuo codice senza saltare attraverso i cerchi addizionali.

Solo una nota:

What I'm trying to do, is not expose the collection - because I don't want the client code to break when the collection changes say from a List to a Set. If I just allow the client code to do class.getCollection() it'll break when the type of the collection changes. So the idea is to make a thin layer around it ("CollectionWrapper"), so when the type of collection changes, only the wrapper has to change, but not the code using the collection (i.e. using the wrapper). Is what I'm saying clear?

Se la raccolta viene fornita solo come interfaccia di raccolta, non si interromperà. Tuttavia , devo sottolineare che i metodi pubblici che si presentano con questa classe, ovvero public Thing getThing(int index) , public void setThing(int index, Thing thing) e public void removeThing(int index) impedirebbero all'implementazione di passare da un elenco a un set , perché gli insiemi non hanno alcun concetto di indici e questi metodi dovrebbero cambiare.

    
risposta data 29.05.2014 - 01:35
fonte
1

Stai descrivendo il modello Collezione incapsulata . Mi piace molto questo schema; è particolarmente appropriato per la progettazione basata su domini.

Il vantaggio che descrivi - essere in grado di modificare la rappresentazione interna della collezione - è buono. Ma penso che il vantaggio più grande sia limitare la capacità dei clienti di modificare o interrompere la raccolta sottostante.

Ad esempio, considera una funzione di "commento" di un social network, con il requisito che non è possibile eliminare i commenti dopo che sono stati pubblicati. Se esporti List<Comment> come proprietà, è facile per i clienti della tua classe chiamare il metodo Remove della lista senza rendersene conto che non avrebbero dovuto.

Se, invece, utilizzi una classe Comments con un metodo% privateList<Comment> e nessun Remove , è ora impossibile interrompere l'invarianza.

Si finisce anche con un modello di dominio più ricco, che è meglio equipaggiato per gestire nuove funzionalità. (Una raccolta di oggetti di dominio è spesso un nuovo concetto di dominio che manca al modello.) Immagina che il requisito sia cambiato e che ora sia possibile eliminare i commenti, ma la cancellazione deve essere registrata in un audit trail. È facile implementare questo comportamento in un nuovo metodo Delete sulla tua classe Comments : sarebbe stato molto più complicato sovrascrivere il metodo Remove preesistente su List .

    
risposta data 30.05.2014 - 09:41
fonte
0

Penso che tu abbia ragione.

Ho sviluppato diverse collezioni personalizzate in linguaggi di programmazione diversi e, a volte, ho applicato il modello. Controllo anche che siano stati applicati anche altri sviluppatori.

Sembra che dipenda da ciò che lo sviluppatore vuole raggiungere, a volte la raccolta è direttamente accessibile, a volte, usando il modello.

I controlli visivi con diversi elementi come "Treeview", "Listview", "Gridview" utilizzano molta di questa tecnica.

    
risposta data 28.05.2014 - 23:23
fonte
0

Hai detto,

What I'm trying to do, is not expose the collection - because I don't want the client code to break when the collection changes say from a List to a Set.

Ho delle preoccupazioni su questo requisito, sia esso percepito o reale.

Le astrazioni rappresentate da quei contenitori standard sono così diverse e diverse, trovo difficile che tu possa sostituire l'una con l'altra nella tua interfaccia. Se SomeClass fornisce un'interfaccia più appropriata per un Set , devi onorare quell'interfaccia indipendentemente da come implementi Set . La stessa logica vale anche per List . In questo senso, non devi affatto esporre ContainerWrapper o Container . Sono dettagli di implementazione di SomeClass . Potresti utilizzare modello Pimpl per nascondere completamente il contenitore.

Forse SomeClass è un contenitore di oggetti con un significato aggiuntivo.

Ad esempio, in un programma CAD, un assieme è costituito da sottocomponenti che possono essere parti o sottoinsiemi. Tuttavia, ciò non è sufficiente per catturare la relazione tra un Assembly e uno dei suoi sub-componenti. L'ubicazione e l'orientamento del sottocomponente nell'assieme costituiscono la parte fondamentale dei dati che rende funzionante questa particolare relazione con contenitore. In questo caso, se si memorizza la raccolta di Pair<sub-component, transform> come elenco o come set, l'interfaccia di Assembly è di pochissima importanza. È necessario essere in grado di interrogare l'Assembly per i suoi sottocomponenti e, dato un sottocomponente, è necessario essere in grado di interrogare l'Assembly per la trasformazione del sottocomponente nell'Assembly.

Un altro esempio: una tela e i relativi oggetti contenuti in un programma di disegno 2D o in un programma SVG. Anche qui, la relazione contenitore-contenuta deve essere creata usando la posizione 2D degli oggetti contenuti più una profondità a per sistemarli a strati. Anche qui, puoi usare un Set o un Elenco per contenere gli oggetti contenuti.

In conclusione, ti sto chiedendo di scavare più a fondo per determinare il tipo di relazione che SomeClass ha con i suoi oggetti contenuti. È una questione di semplice contenimento o c'è di più nella relazione? Se c'è di più nella relazione, come potresti esporre la relazione che è divorziata da una Lista o da un Set.

In entrambi gli esempi che ho presentato, una lista ha più senso per me. Non posso dire cosa abbia più senso per SomeClass .

    
risposta data 30.05.2014 - 04:57
fonte

Leggi altre domande sui tag