Esiste un modello per un modo più "naturale" di aggiungere elementi alle raccolte? [chiuso]


13

Penso che il modo più comune di aggiungere qualcosa a una raccolta sia utilizzare un tipo di Addmetodo fornito da una raccolta:

class Item {}    
var items = new List<Item>();
items.Add(new Item());

e in realtà non c'è nulla di insolito in questo.

Mi chiedo comunque perché non lo facciamo in questo modo:

var item = new Item();
item.AddTo(items);

sembra in qualche modo più naturale del primo metodo. Ciò avrebbe il vantaggio che quando la Itemclasse ha una proprietà come Parent:

class Item 
{
    public object Parent { get; private set; }
} 

puoi rendere privato il setter. In questo caso, ovviamente, non è possibile utilizzare un metodo di estensione.

Ma forse mi sbaglio e non ho mai visto questo schema prima perché è così insolito? Sai se esiste un tale schema?

In C#un metodo di estensione sarebbe utile per questo

public static T AddTo(this T item, IList<T> list)
{
    list.Add(item);
    return item;
}

Che ne dici di altre lingue? Immagino che nella maggior parte di essi la Itemclasse abbia dovuto fornire un'interfaccia chiamiamola così ICollectionItem.


Update-1

Ci ho pensato un po 'di più e questo modello sarebbe davvero utile, ad esempio, se non si desidera aggiungere un elemento a più raccolte.

ICollectableinterfaccia di test :

interface ICollectable<T>
{
    // Gets a value indicating whether the item can be in multiple collections.
    bool CanBeInMultipleCollections { get; }

    // Gets a list of item's owners.
    List<ICollection<T>> Owners { get; }

    // Adds the item to a collection.
    ICollectable<T> AddTo(ICollection<T> collection);

    // Removes the item from a collection.
    ICollectable<T> RemoveFrom(ICollection<T> collection);

    // Checks if the item is in a collection.
    bool IsIn(ICollection<T> collection);
}

e un'implementazione di esempio:

class NodeList : List<NodeList>, ICollectable<NodeList>
{
    #region ICollectable implementation.

    List<ICollection<NodeList>> owners = new List<ICollection<NodeList>>();

    public bool CanBeInMultipleCollections
    {
        get { return false; }
    }

    public ICollectable<NodeList> AddTo(ICollection<NodeList> collection)
    {
        if (IsIn(collection))
        {
            throw new InvalidOperationException("Item already added.");
        }

        if (!CanBeInMultipleCollections)
        {
            bool isInAnotherCollection = owners.Count > 0;
            if (isInAnotherCollection)
            {
                throw new InvalidOperationException("Item is already in another collection.");
            }
        }
        collection.Add(this);
        owners.Add(collection);
        return this;
    }

    public ICollectable<NodeList> RemoveFrom(ICollection<NodeList> collection)
    {
        owners.Remove(collection);
        collection.Remove(this);
        return this;
    }

    public List<ICollection<NodeList>> Owners
    {
        get { return owners; }
    }

    public bool IsIn(ICollection<NodeList> collection)
    {
        return collection.Contains(this);
    }

    #endregion
}

utilizzo:

var rootNodeList1 = new NodeList();
var rootNodeList2 = new NodeList();

var subNodeList4 = new NodeList().AddTo(rootNodeList1);

// Let's move it to the other root node:
subNodeList4.RemoveFrom(rootNodeList1).AddTo(rootNodeList2);

// Let's try to add it to the first root node again... 
// and it will throw an exception because it can be in only one collection at the same time.
subNodeList4.AddTo(rootNodeList1);

13
item.AddTo(items)supponiamo di avere una lingua senza metodi di estensione: naturale o no, per supportare addTo ogni tipo avrebbe bisogno di questo metodo e fornirlo per ogni tipo di raccolta che supporta l'aggiunta. Questo è come il miglior esempio di introduzione di dipendenze tra tutto ciò che io abbia mai sentito: P - Penso che la falsa premessa qui stia cercando di modellare un po 'di astrazione programmatica nella vita "reale". Questo spesso va storto.
Stijn

1
@ t3chb0t Hehe, buon punto. Mi chiedevo se la tua prima lingua parlata influenza quale costrutto sembra più naturale. Per me, l'unico che sembra naturale sarebbe add(item, collection), ma non è un buon stile OO.
Kilian Foth,

3
@ t3chb0t Nella lingua parlata, puoi leggere le cose normalmente o in modo riflessivo. "John, per favore aggiungi questa mela a quello che stai portando." vs "La mela dovrebbe essere aggiunta a ciò che stai portando, John." Nel mondo di OOP, la riflessione non ha molto senso. Non chiedi a qualcosa di essere influenzato da un altro oggetto in generale. Il più vicino a questo in OOP è il modello di visitatore . C'è una ragione per cui questa non è la norma.
Neil,

3
Questo sta facendo un bel inchino attorno a una cattiva idea .
Telastyn,

3
@ t3chb0t Potresti trovare un'interpretazione interessante del Regno dei sostantivi .
paul

Risposte:


51

No, item.AddTo(items)non è più naturale. Penso che mescoli questo con il seguente:

t3chb0t.Add(item).To(items)

Hai ragione perché items.Add(item)non è molto vicino alla lingua inglese naturale. Ma anche tu non ascolti item.AddTo(items)in inglese naturale, vero? Normalmente c'è qualcuno che dovrebbe aggiungere l'elemento alla lista. Sia quando si lavora in un supermercato o mentre si cucina e si aggiungono ingredienti.

Nel caso dei linguaggi di programmazione, abbiamo creato un elenco che fa entrambe le cose: archiviare i suoi elementi ed essere responsabile per aggiungerli a se stesso.

Il problema con il tuo approccio è che un elemento deve essere consapevole dell'esistenza di un elenco. Ma un oggetto potrebbe esistere anche se non ci fossero liste, giusto? Ciò dimostra che non dovrebbe essere a conoscenza degli elenchi. Gli elenchi non devono essere presenti nel suo codice in alcun modo.

Le liste tuttavia non esistono senza oggetti (almeno sarebbero inutili). Pertanto va bene se conoscono i loro articoli - almeno in una forma generica.


4

@valenterry ha ragione sul problema della separazione delle preoccupazioni. Le classi non dovrebbero sapere nulla delle varie raccolte che potrebbero contenerle. Non dovresti cambiare la classe di oggetti ogni volta che crei un nuovo tipo di raccolta.

Detto ciò...

1. Non Java

Java non ti aiuta qui, ma alcune lingue hanno una sintassi più flessibile ed estensibile.

In Scala, items.add (item) è simile al seguente:

  item :: items

Where :: è un operatore che aggiunge elementi agli elenchi. Sembra proprio quello che vuoi. In realtà è zucchero sintattico per

  items.::(item)

dove :: è un metodo di elenco Scala che è effettivamente equivalente a list.add di Java . Quindi, mentre nella prima versione sembra che l' articolo si stia aggiungendo agli articoli , in verità gli articoli stanno facendo l'aggiunta. Entrambe le versioni sono valide Scala

Questo trucco si svolge in due passaggi:

  1. A Scala, gli operatori sono davvero solo metodi. Se si importano elenchi Java in Scala, allora items.add (oggetto) può anche essere scritto oggetti aggiungendo oggetto . In Scala, 1 + 2 è in realtà 1. + (2) . Nella versione con spazi anziché punti e parentesi, Scala vede il primo oggetto, cerca un metodo che corrisponda alla parola successiva e (se il metodo accetta parametri) passa la cosa successiva (o cose) a quel metodo.
  2. A Scala, gli operatori che terminano in due punti sono associativi a destra piuttosto che a sinistra. Quindi quando Scala vede il metodo, cerca il proprietario del metodo a destra e inserisce il valore a sinistra.

Se non ti senti a tuo agio con molti operatori e desideri aggiungere AddTo , Scala offre un'altra opzione: conversioni implicite. Ciò richiede due passaggi

  1. Crea una classe wrapper chiamata, diciamo, ListItem che accetta un elemento nel suo costruttore e ha un metodo addTo che può aggiungere quell'elemento a un determinato elenco.
  2. Creare una funzione che prende un oggetto come un parametro e restituisce un ListItem contenente tale elemento . Contrassegnalo come implicito .

Se le conversioni implicite sono abilitate e Scala vede

  item addTo items

o

  item.addTo(items)

e l'elemento non ha addTo , cercherà nell'ambito corrente eventuali funzioni di conversione implicite. Se una qualsiasi delle funzioni di conversione restituisce un tipo che ha avere un Aggiungial metodo, questo accade:

  ListItem.(item).addTo(items)

Ora, potresti trovare che le conversioni implicite indirizzano di più ai tuoi gusti, ma aggiunge pericolo, perché il codice può fare cose inaspettate se non sei chiaramente consapevole di tutte le conversioni implicite in un determinato contesto. Ecco perché questa funzione non è più abilitata per impostazione predefinita in Scala. (Tuttavia, mentre puoi scegliere se abilitarlo o meno nel tuo codice, non puoi fare nulla per il suo stato nelle librerie di altre persone. Qui ci sono draghi).

Gli operatori con terminazione di due punti sono più brutti ma non rappresentano una minaccia.

Entrambi gli approcci preservano il principio di separazione delle preoccupazioni. Puoi far sembrare che l' oggetto sia a conoscenza della collezione, ma il lavoro viene effettivamente svolto altrove. Qualsiasi altra lingua che desideri darti questa funzionalità dovrebbe essere almeno altrettanto attenta.

2. Java

In Java, dove la sintassi non è estendibile da te, il meglio che puoi fare è creare una sorta di classe wrapper / decoratore che conosca le liste. Nel dominio in cui i tuoi articoli sono inseriti in elenchi, puoi includerli in quello. Quando lasciano quel dominio, eliminali. Forse il modello di visitatore è il più vicino.

3. tl; dr

Scala, come alcune altre lingue, può aiutarti con una sintassi più naturale. Java può offrirti solo brutti motivi barocchi.


2

Con il tuo metodo di estensione devi ancora chiamare Add () in modo che non aggiunga nulla di utile al tuo codice. A meno che il tuo metodo addto non abbia fatto qualche altra elaborazione non c'è motivo per esistere. E scrivere codice che non ha scopi funzionali è dannoso per la leggibilità e la manutenibilità.

Se lo lasci non generico, i problemi diventano ancora più evidenti. Ora hai un oggetto che deve conoscere i diversi tipi di collezioni in cui può trovarsi. Ciò viola il principio della singola responsabilità. Non solo un oggetto è un detentore dei suoi dati, ma manipola anche le raccolte. Questo è il motivo per cui lasciamo la responsabilità di aggiungere elementi alle raccolte fino al pezzo di codice che li sta consumando entrambi.


Se un framework riconoscesse diversi tipi di riferimenti a oggetti (ad esempio distinguendo tra quelli che incapsulano la proprietà e quelli che non lo fanno), potrebbe avere senso disporre di un mezzo con cui un riferimento che incapsula la proprietà potrebbe cedere la proprietà a una collezione che era capace di riceverlo. Se ogni cosa è limitata al possesso di un solo proprietario, il trasferimento della proprietà tramite thing.giveTo(collection)[e la necessità collectiondi implementare un'interfaccia "acceptOwnership"] avrebbe più senso che collectionincludere un metodo che ne impadroniva. Certo ...
supercat

... la maggior parte delle cose in Java non ha un concetto di proprietà e un riferimento a un oggetto può essere archiviato in una raccolta senza coinvolgere l'oggetto così identificato.
supercat

1

Penso che tu sia ossessionato dalla parola "aggiungi". Prova invece a cercare una parola alternativa, come "raccogliere", che ti darebbe una sintassi più adatta all'inglese senza alcun cambiamento nel modello:

items.collect(item);

Il metodo sopra è esattamente lo stesso di items.add(item);, con l'unica differenza che è la scelta di parole diverse, e scorre molto bene e "naturalmente" in inglese.

A volte, semplicemente rinominare variabili, metodi, classi, ecc. Impedirà la riprogettazione fiscale del modello.


collectviene talvolta utilizzato come nome per metodi che riducono una raccolta di elementi in una sorta di risultato singolare (che può anche essere una raccolta). Questa convenzione è stata adottata dall'API Java Steam , che ha reso immensamente popolare l'utilizzo del nome in questo contesto. Non ho mai visto la parola usata nel contesto che menzioni nella tua risposta. Preferirei restare con addoput
toniedzwiedz il

@toniedzwiedz, Quindi considera pusho enqueue. Il punto non è il nome di esempio specifico, ma il fatto che un nome diverso potrebbe essere più vicino al desiderio del PO di grammatica inglese.
Brian S,

1

Sebbene non esista tale modello per il caso generale (come spiegato da altri), il contesto in cui un modello simile ha i suoi meriti è un'associazione bidirezionale uno a molti in ORM.

In questo contesto avresti due entità, diciamo Parente Child. Un genitore può avere molti figli, ma ogni figlio appartiene a un genitore. Se l'applicazione richiede che l'associazione sia navigabile da entrambe le estremità (bidirezionale), avresti a List<Child> childrennella classe genitore e a Parent parentnella classe figlio.

L'associazione dovrebbe sempre essere reciproca, ovviamente. Le soluzioni ORM in genere lo applicano alle entità che caricano. Ma quando l'applicazione sta manipolando un'entità (persistente o nuova), deve applicare le stesse regole. Nella mia esperienza, troverai spesso un Parent.addChild(child)metodo che invoca Child.setParent(parent), che non è per uso pubblico. In quest'ultimo caso potresti trovare gli stessi controlli implementati nel tuo esempio. L'altra route può tuttavia essere implementata anche: Child.addTo(parent)quali chiamate Parent.addChild(child). Quale percorso è meglio differisce da una situazione all'altra.


1

In Java, una raccolta tipica non contiene oggetti : contiene riferimenti ad oggetti o copie più precise di riferimenti ad oggetti. La sintassi dei membri del punto, al contrario, è generalmente usata per agire sulla cosa identificata da un riferimento, piuttosto che sul riferimento stesso.

Posizionare una mela in una ciotola altererebbe alcuni aspetti della mela (ad es. La sua posizione), posizionando una striscia di carta che dice "oggetto # 29521" in una ciotola non cambierebbe la mela in alcun modo, anche se capita di essere oggetto # 29521. Inoltre, poiché le raccolte contengono copie di riferimenti, non esiste un equivalente di Java a mettere un foglietto con scritto "oggetto # 29521" in una ciotola. Invece, si copia il numero # 29521 in una nuova distinta di carta e si mostra (non si dà) quello nella ciotola, che farà loro la sua copia, lasciando inalterato l'originale.

Infatti, poiché i riferimenti agli oggetti (i "foglietti di carta") in Java possono essere osservati passivamente, né i riferimenti, né gli oggetti così identificati, hanno alcun modo di sapere cosa viene fatto con essi. Esistono alcuni tipi di raccolte che, anziché limitarsi a contenere un mucchio di riferimenti a oggetti che potrebbero non sapere nulla dell'esistenza della raccolta, stabiliscono invece una relazione bidirezionale con gli oggetti incapsulati in essa. Con alcune di queste raccolte, può avere senso chiedere a un metodo di aggiungersi a una raccolta (specialmente se un oggetto è in grado di trovarsi in una di queste raccolte alla volta). In generale, tuttavia, la maggior parte delle raccolte non si adatta a quel modello e può accettare riferimenti a oggetti senza alcun coinvolgimento da parte degli oggetti identificati da tali riferimenti.


questo post è piuttosto difficile da leggere (wall of text). Ti dispiacerebbe modificarlo in una forma migliore?
moscerino del

@gnat: è meglio?
supercat

1
@gnat: Siamo spiacenti: un singhiozzo di rete ha provocato il fallimento del mio tentativo di pubblicare la modifica. Ti piace di più adesso?
supercat

-2

item.AddTo(items)non viene utilizzato perché è passivo. Cf "L'articolo viene aggiunto all'elenco" e "la stanza è dipinta dal decoratore".

Le costruzioni passive vanno bene, ma, almeno in inglese, tendiamo a preferire le costruzioni attive.

Nella programmazione OO, il paradigma mette in risalto gli attori, non quelli su cui si è agito, così abbiamo items.Add(item). L'elenco è la cosa che fa qualcosa. È l'attore, quindi questo è il modo più naturale.


Dipende da dove ti trovi ;-) - la teoria della relatività - potresti dire lo stesso di list.Add(item) un elemento che viene aggiunto all'elenco o di un elenco che aggiunge un elemento o item.AddTo(list) dell'elemento si aggiunge all'elenco o aggiungo l'articolo all'elenco o come hai detto, l'elemento viene aggiunto all'elenco : tutti sono corretti. Quindi la domanda è: chi è l'attore e perché? Un oggetto è anche un attore e vuole unirsi a un gruppo (elenco), non il gruppo vuole avere questo oggetto ;-) l'oggetto agisce attivamente per far parte di un gruppo.
t3chb0t,

L'attore è la cosa che fa la cosa attiva. "Volere" è una cosa passiva. Nella programmazione, è l'elenco che cambia quando un elemento viene aggiunto ad esso, l'elemento non dovrebbe cambiare affatto.
Matt Ellen,

... anche se spesso gli piace quando ha una Parentproprietà. Certo, l'inglese non è la lingua madre, ma per quanto ne so, il desiderio non è passivo, a meno che non sia desiderato o che non voglia che io faccia qualcosa ; -]
t3chb0t

Quale azione esegui quando vuoi qualcosa? Questo è il mio punto. Non è necessariamente grammaticalmente passivo, ma semanticamente lo è. Attributi padre WRT, sicuro che è un'eccezione, ma tutte le euristiche li hanno.
Matt Ellen,

Ma List<T>(almeno quello trovato in System.Collections.Generic) non aggiorna alcuna Parentproprietà. Come potrebbe, se dovrebbe essere generico? Di solito è responsabilità del contenitore aggiungere il suo contenuto.
Arturo Torres Sánchez,
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.