Esiste uno schema per un modo più "naturale" di aggiungere oggetti alle collezioni? [chiuso]

13

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

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

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

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

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

sembra essere in qualche modo più naturale quindi il primo metodo. Ciò avrebbe il vantaggio che quando la classe Item 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 ho sbagliato e non ho mai visto questo modello prima perché è così raro? Sai se esiste uno schema del genere?

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? Suppongo che nella maggior parte di essi la classe Item debba fornire una chiamiamola ICollectionItem interface.

Aggiornamento-1

Ci stavo pensando un po 'di più e questo modello sarebbe davvero utile per esempio se non vuoi che un articolo venga aggiunto a più raccolte.

test ICollectable interface:

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);
    
posta t3chb0t 15.01.2015 - 09:35
fonte

7 risposte

51

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

t3chb0t.Add(item).To(items)

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

Nel caso dei linguaggi di programmazione, abbiamo fatto in modo che una lista facesse entrambe le cose: memorizzando i suoi elementi e come responsabili della loro aggiunta a se stessa.

Il problema con il tuo approccio è che un elemento deve essere consapevole del fatto che esiste un elenco. Ma un oggetto potrebbe esistere anche se non ci fossero liste, giusto? Questo dimostra che non dovrebbe essere a conoscenza delle liste. Gli elenchi non dovrebbero assolutamente comparire nel suo codice.

Le liste tuttavia non esistono senza elementi (almeno sarebbero inutili). Quindi va bene se sanno dei loro articoli - almeno in una forma generica.

    
risposta data 15.01.2015 - 09:46
fonte
4

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

Detto questo ...

1. Non Java

Java non ti è d'aiuto, ma alcune lingue hanno una sintassi più flessibile ed estensibile.

In Scala, items.add (elemento) ha il seguente aspetto:

  item :: items

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

  items.::(item)

dove :: è un metodo di elenco Scala che è effettivamente equivalente a list.add di Java. Così mentre appare nella prima versione come se elemento si aggiungesse a elementi , in realtà elementi sta facendo l'aggiunta. Entrambe le versioni sono valide Scala

Questo trucco viene eseguito in due passaggi:

  1. In Scala, gli operatori sono solo metodi. Se importi gli elenchi Java in Scala, allora items.add (elemento) può anche essere scritto elementi aggiungi elemento . 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 le cose) a quel metodo.
  2. In 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 sulla destra e inserisce il valore a sinistra.

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

  1. Crea una classe wrapper chiamata, ad esempio, ListItem che accetta un elemento nel suo costruttore e ha un metodo addTo che può aggiungere quell'elemento in una lista data.
  2. Crea una funzione che accetta un elemento come parametro e restituisce un ID elenco contenente quell'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à l'ambito corrente per qualsiasi funzione di conversione implicita. Se una qualsiasi delle funzioni di conversione restituisce un tipo che ha ha un metodo addTo , ciò accade:

  ListItem.(item).addTo(items)

Ora, potresti scoprire che le conversioni implicite si indirizzano più a tuo gusto, ma aggiunge pericolo, perché il codice può fare cose inaspettate se non sei chiaramente consapevole di tutte le conversioni implicite in un determinato contesto. Questo è il motivo per cui questa funzione non è più abilitata di default in Scala. (Tuttavia, mentre puoi scegliere se abilitarlo o meno nel tuo codice personale, non puoi fare nulla per il suo stato nelle librerie di altre persone. Qui si trovano i draghi).

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

Entrambi gli approcci preservano il principio della separazione delle preoccupazioni. Puoi renderlo look come se item fosse a conoscenza della raccolta, ma il lavoro viene effettivamente svolto da qualche altra parte. Qualsiasi altra lingua che vuole darti questa funzionalità dovrebbe essere almeno altrettanto prudente.

2. Java

In Java, dove la sintassi non è estendibile da te, la cosa migliore 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 racchiuderli in questo. Quando lasciano quel dominio, portali fuori. Forse il modello visitatore è il più vicino.

3. tl; dr

Scala, come altri linguaggi, può aiutarti con una sintassi più naturale. Java può offrirti solo modelli brutti e barocchi.

    
risposta data 15.01.2015 - 15:50
fonte
2

Con il tuo metodo di estensione devi comunque chiamare Add () in modo che non aggiunga nulla di utile al tuo codice. A meno che il tuo metodo addto non abbia fatto altre elaborazioni, non c'è motivo di esistere. E scrivere codice che non ha uno scopo funzionale è negativo per la leggibilità e la manutenibilità.

Se lo lasci non generico i problemi diventano ancora più evidenti. Ora hai un articolo che deve conoscere i diversi tipi di collezioni in cui può essere inserito. Questo viola il principio della responsabilità unica. Non solo un oggetto è un supporto per i suoi dati, ma manipola anche le raccolte. Ecco perché lasciamo la responsabilità di aggiungere elementi alle raccolte fino al pezzo di codice che sta consumando entrambi.

    
risposta data 15.01.2015 - 09:51
fonte
1

Penso che tu sia ossessionato dalla parola "aggiungi". Prova invece a cercare una parola alternativa, come "collect", che ti darebbe una sintassi più amichevole con l'inglese senza modifiche nel pattern:

items.collect(item);

Il metodo sopra è esattamente uguale a items.add(item); , con l'unica differenza che è una diversa scelta di parole, e scorre molto bene e "naturalmente" in inglese.

A volte, solo la ridenominazione di variabili, metodi, classi, ecc. impedirà la riprogettazione della tassazione del modello.

    
risposta data 15.01.2015 - 17:07
fonte
0

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

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

L'associazione dovrebbe sempre essere reciproca, ovviamente. Le soluzioni ORM in genere applicano questo alle entità che caricano. Ma quando l'applicazione sta manipolando un'entità (persistente o nuova), deve applicare le stesse regole. Nella mia esperienza, troveremmo più spesso un metodo Parent.addChild(child) che richiama Child.setParent(parent) , che non è per uso pubblico. In quest'ultimo caso normalmente trovi gli stessi controlli che hai implementato nel tuo esempio. L'altra rotta può tuttavia essere implementata anche: Child.addTo(parent) che chiama Parent.addChild(child) . Quale percorso è il migliore differisce da situazione a situazione.

    
risposta data 15.01.2015 - 22:56
fonte
0

In Java, una raccolta tipica non contiene cose - contiene riferimenti alle cose, o più precisamente copie di riferimenti alle cose. La sintassi del membro del puntino, al contrario, viene generalmente utilizzata per agire sulla cosa identificata da un riferimento, piuttosto che sul riferimento stesso.

Se si posiziona una mela in una ciotola si alterano alcuni aspetti della mela (ad esempio la sua posizione), mettendo un pezzo di carta che dice "oggetto # 29521" in una ciotola non cambierebbe la mela in alcun modo, anche se capita di essere l'oggetto # 29521. Inoltre, poiché le collezioni contengono copie di riferimenti, non c'è Java equivalente a mettere un pezzo di carta che dice "oggetto # 29521" in una ciotola. Invece, uno copia il numero # 29521 in un nuovo foglietto di carta e mostra (non dare) quello alla ciotola, che farà loro la propria copia, lasciando inalterato l'originale.

In effetti, poiché i riferimenti oggetto (i "foglietti di carta") in Java possono essere osservati passivamente, né i riferimenti, né gli oggetti identificati in tal modo, hanno alcun modo di sapere che cosa si sta facendo con loro. Esistono alcuni tipi di raccolte che, anziché limitarsi a contenere un gruppo di riferimenti a oggetti che potrebbero non conoscere nulla dell'esistenza della raccolta, stabiliscono invece una relazione bidirezionale con gli oggetti incapsulati in essa. Con alcune di queste raccolte, può essere sensato chiedere un metodo per aggiungersi a una raccolta (specialmente se un oggetto è in grado di trovarsi in una tale collezione alla volta). In generale, tuttavia, la maggior parte delle raccolte non si adatta a questo modello e può accettare riferimenti a oggetti senza alcun coinvolgimento da parte degli oggetti identificati da tali riferimenti.

    
risposta data 15.01.2015 - 17:33
fonte
-2

item.AddTo(items) non è usato perché è passivo. C.f. "L'articolo è aggiunto alla lista" e "la stanza è dipinta dal decoratore".

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

Nella programmazione OO, il pardigm dà risalto agli attori, non quelli su cui agisce, quindi abbiamo items.Add(item) . La lista è la cosa che fa qualcosa. È l'attore, quindi questo è il modo più naturale.

    
risposta data 15.01.2015 - 09:52
fonte

Leggi altre domande sui tag