¿Existe un patrón para una forma más "natural" de agregar elementos a las colecciones? [cerrado]

13

Creo que la forma más común de agregar algo a una colección es usar algún tipo de Addmétodo que proporcione una colección:

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

y en realidad no hay nada inusual en eso.

Sin embargo, me pregunto por qué no lo hacemos de esta manera:

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

parece ser de alguna manera más natural que el primer método. Esto tendría el riesgo de que cuando la Itemclase tenga una propiedad como Parent:

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

puedes hacer que el setter sea privado. En este caso, por supuesto, no puede utilizar un método de extensión.

Pero tal vez me equivoque y nunca antes haya visto este patrón porque es muy poco común. ¿Sabes si hay tal patrón?

En C#un método de extensión sería útil para eso

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

¿Qué tal otros idiomas? Supongo que en la mayoría de ellos la Itemclase tenía que proporcionar una ICollectionIteminterfaz llamada .


Actualización-1

Lo he estado pensando un poco más y este patrón sería realmente útil, por ejemplo, si no desea que se agregue un elemento a varias colecciones.

ICollectableinterfaz de prueba :

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);
}

y una implementación de muestra:

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
}

uso:

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);
t3chb0t
fuente
13
item.AddTo(items)supongamos que tiene un lenguaje sin métodos de extensión: natural o no, para admitir addTo, todo tipo necesitaría este método y lo proporcionaría para cada tipo de colección que admita anexos. Ese es como el mejor ejemplo de introducción de dependencias entre todo lo que he escuchado: P - Creo que la premisa falsa aquí es tratar de modelar alguna abstracción de programación para la vida 'real'. Eso a menudo sale mal.
stijn
1
@ t3chb0t Jeje, buen punto. Me preguntaba si su primer idioma hablado influye en qué construcción parece más natural. Para mí, el único que parece natural sería add(item, collection), pero ese no es un buen estilo OO.
Kilian Foth
3
@ t3chb0t En el lenguaje hablado, puedes leer cosas normalmente o reflexivamente. "John, por favor agrega esta manzana a lo que llevas". vs "La manzana debe agregarse a lo que llevas, John". En el mundo de OOP, reflexivo no tiene mucho sentido. No pides que algo se vea afectado por otro objeto en general. Lo más cercano a esto en OOP es el patrón de visitante . Sin embargo, hay una razón por la que esta no es la norma.
Neil
3
Esto es poner una buena reverencia alrededor de una mala idea .
Telastyn
3
@ t3chb0t Es posible que el Reino de los sustantivos diga una lectura interesante.
Paul

Respuestas:

51

No, item.AddTo(items)no es más natural. Creo que mezclas esto con lo siguiente:

t3chb0t.Add(item).To(items)

Tienes razón en que items.Add(item)no está muy cerca del idioma inglés natural. Pero tampoco escuchas item.AddTo(items)en inglés natural, ¿verdad? Normalmente hay alguien que supone agregar el elemento a la lista. Ya sea cuando trabaje en un supermercado o mientras cocina y agrega ingredientes.

En el caso de los lenguajes de programación, lo hicimos para que una lista haga ambas cosas: almacenar sus elementos y ser responsables de agregarlos a sí mismo.

El problema con su enfoque es que un elemento debe ser consciente de que existe una lista. Pero un elemento podría existir incluso si no hubiera listas, ¿verdad? Esto muestra que no debe tener en cuenta las listas. Las listas no deben aparecer en su código de ninguna manera.

Sin embargo, las listas no existen sin elementos (al menos serían inútiles). Por lo tanto, está bien si conocen sus artículos, al menos en forma genérica.

valenterry
fuente
4

@valenterry tiene toda la razón sobre el problema de la separación de preocupaciones. Las clases no deben saber nada sobre las diversas colecciones que pueden contenerlas. No querrá tener que cambiar la clase de elemento cada vez que cree un nuevo tipo de colección.

Dicho eso ...

1. No es Java

Java no es de ayuda aquí, pero algunos lenguajes tienen una sintaxis más flexible y extensible.

En Scala, items.add (item) se ve así:

  item :: items

Where :: es un operador que agrega elementos a las listas. Eso parece mucho lo que quieres. En realidad es azúcar sintáctica para

  items.::(item)

where :: es un método de lista Scala que es efectivamente equivalente al list.add de Java . Entonces, si bien en la primera versión parece que el elemento se agrega a los elementos , en realidad, los elementos están agregando. Ambas versiones son válidas Scala

Este truco se realiza en dos pasos:

  1. En Scala, los operadores son realmente solo métodos. Si importa listas Java a Scala, items.add (item) también se pueden escribir ítems agregar ítem . En Scala, 1 + 2 es en realidad 1. + (2) . En la versión con espacios en lugar de puntos y corchetes, Scala ve el primer objeto, busca un método que coincida con la siguiente palabra y (si el método toma parámetros) pasa la siguiente cosa (o cosas) a ese método.
  2. En Scala, los operadores que terminan en dos puntos son asociativos a la derecha en lugar de a la izquierda. Entonces, cuando Scala ve el método, busca al propietario del método a la derecha y lo introduce en el valor de la izquierda.

Si no se siente cómodo con muchos operadores y desea su addTo , Scala ofrece otra opción: conversiones implícitas. Esto requiere dos pasos

  1. Cree una clase de contenedor llamada, digamos, ListItem que toma un elemento en su constructor y tiene un método addTo que puede agregar ese elemento a una lista dada.
  2. Cree una función que tome un elemento como parámetro y devuelva un ListItem que contenga ese elemento . Marcarlo como implícito .

Si las conversiones implícitas están habilitadas y Scala ve

  item addTo items

o

  item.addTo(items)

y el elemento no tiene addTo , buscará en el ámbito actual cualquier función de conversión implícita. Si alguna de las funciones de conversión devuelve un tipo que hace tener un AddTo método, esto sucede:

  ListItem.(item).addTo(items)

Ahora, puede encontrar que las conversiones implícitas se enrutan más a su gusto, pero agrega peligro, porque el código puede hacer cosas inesperadas si no es consciente de todas las conversiones implícitas en un contexto dado. Es por eso que esta función ya no está habilitada de manera predeterminada en Scala. (Sin embargo, aunque puede elegir habilitarlo o no en su propio código, no puede hacer nada acerca de su estado en las bibliotecas de otras personas. Aquí hay dragones).

Los operadores terminados en dos puntos son más feos pero no representan una amenaza.

Ambos enfoques preservan el principio de separación de preocupaciones. Puede hacer que parezca que el artículo conoce la colección, pero el trabajo se realiza en otro lugar. Cualquier otro idioma que quiera darle esta característica debe ser al menos tan cuidadoso.

2. Java

En Java, donde la sintaxis no es extensible por usted, lo mejor que puede hacer es crear algún tipo de clase de envoltura / decorador que conozca las listas. En el dominio donde se colocan sus elementos en listas, puede envolverlos en eso. Cuando salgan de ese dominio, sácalos. Quizás el patrón de visitante es el más cercano.

3. tl; dr

Scala, como algunos otros idiomas, puede ayudarlo con una sintaxis más natural. Java solo puede ofrecerte patrones feos y barrocos.

itsbruce
fuente
2

Con su método de extensión aún debe llamar a Add () para que no agregue nada útil a su código. A menos que su método addto haya realizado algún otro procesamiento, no hay razón para que exista. Y escribir código que no tiene un propósito funcional es malo para la legibilidad y la mantenibilidad.

Si lo deja no genérico, los problemas se vuelven aún más evidentes. Ahora tiene un elemento que necesita saber sobre los diferentes tipos de colecciones en las que puede estar. Eso viola el principio de responsabilidad única. Un elemento no solo es un titular de sus datos, sino que también manipula colecciones. Es por eso que dejamos la responsabilidad de agregar elementos a las colecciones hasta el código que los consume a ambos.

controlar
fuente
Si un marco reconociera diferentes tipos de referencias a objetos (por ejemplo, distinguir entre aquellos que encapsulan la propiedad y los que no), podría tener sentido tener un medio por el cual una referencia que encapsula la propiedad podría renunciar a la propiedad de una colección que fue capaz de recibirlo. Si cada cosa se limita a tener un solo propietario, transferir la propiedad a través de thing.giveTo(collection)[y requerir la collectionimplementación de una interfaz "acceptOwnership"] tendría más sentido que collectionincluir un método que tomara posesión. Por supuesto ...
supercat
... la mayoría de las cosas en Java no tienen un concepto de propiedad, y una referencia de objeto puede almacenarse en una colección sin involucrar al objeto identificado de ese modo.
supercat
1

Creo que estás obsesionado con la palabra "agregar". Intente buscar una palabra alternativa en su lugar, como "recoger", que le daría una sintaxis más amigable con el inglés sin cambios en el patrón:

items.collect(item);

El método anterior es exactamente el mismo items.add(item);, con la única diferencia de ser una elección de palabras diferente, y fluye muy bien y "naturalmente" en inglés.

A veces, simplemente cambiar el nombre de las variables, métodos, clases, etc. evitará el rediseño impositivo del patrón.

Descansa en paz
fuente
collecta veces se usa como el nombre de métodos que reducen una colección de elementos en algún tipo de resultado singular (que también puede ser una colección). Esta convención fue adoptada por la API Java Steam , que hizo que el uso del nombre en este contexto fuera inmensamente popular. Nunca he visto la palabra utilizada en el contexto que mencionas en tu respuesta. Prefiero seguir con addoput
toniedzwiedz
@toniedzwiedz, así que considere pusho enqueue. El punto no es el nombre de ejemplo específico, sino el hecho de que un nombre diferente podría estar más cerca del deseo del OP por una gramática al estilo inglés.
Brian S
1

Aunque no existe tal patrón para el caso general (como explicaron otros), el contexto en el que un patrón similar tiene sus méritos es una asociación bidireccional de uno a muchos en ORM.

En este contexto, tendría dos entidades, digamos Parenty Child. Un padre puede tener muchos hijos, pero cada hijo pertenece a uno de los padres. Si la aplicación requiere que la asociación sea navegable desde ambos extremos (bidireccional), tendría una List<Child> childrenen la clase principal y una Parent parenten la clase secundaria.

La asociación siempre debe ser recíproca, por supuesto. Las soluciones ORM generalmente aplican esto en las entidades que cargan. Pero cuando la aplicación está manipulando una entidad (ya sea persistente o nueva), tiene que aplicar las mismas reglas. En mi experiencia, a menudo encontrarás un Parent.addChild(child)método que invoca Child.setParent(parent), que no es para uso público. En este último, normalmente encontrará las mismas comprobaciones que ha implementado en su ejemplo. Sin embargo, también se puede implementar la otra ruta: Child.addTo(parent)qué llamadas Parent.addChild(child). La ruta que mejor difiere de una situación a otra.

Maarten Winkels
fuente
1

En Java, una colección típica no contiene cosas: contiene referencias a cosas, o copias más precisas de referencias a cosas. La sintaxis de miembro de punto, por el contrario, generalmente se usa para actuar sobre la cosa identificada por una referencia, en lugar de sobre la referencia misma.

Si bien colocar una manzana en un tazón alteraría algunos aspectos de la manzana (por ejemplo, su ubicación), colocar un trozo de papel que diga "objeto # 29521" en un tazón no cambiaría la manzana de ninguna manera, incluso si resulta ser objeto # 29521. Además, debido a que las colecciones contienen copias de referencias, no existe un equivalente de Java para colocar un trozo de papel que diga "objeto # 29521" en un recipiente. En cambio, uno copia el número # 29521 en un nuevo trozo de papel y muestra (no da) eso en el tazón, lo que hará que haga su propia copia, sin afectar el original.

De hecho, dado que las referencias a objetos (los "trozos de papel") en Java pueden observarse pasivamente, ni las referencias ni los objetos identificados de este modo tienen forma de saber qué se está haciendo con ellos. Hay algunos tipos de colecciones que, en lugar de simplemente contener un montón de referencias a objetos que pueden no saber nada de la existencia de la colección, establecen una relación bidireccional con los objetos encapsulados en ella. Con algunas de estas colecciones, puede tener sentido pedirle a un método que se agregue a una colección (especialmente si un objeto solo es capaz de estar en una de esas colecciones a la vez). Sin embargo, en general, la mayoría de las colecciones no se ajustan a ese patrón y pueden aceptar referencias a objetos sin ninguna participación por parte de los objetos identificados por esas referencias.

Super gato
fuente
esta publicación es bastante difícil de leer (muro de texto). ¿Te importaría editarlo en una mejor forma?
mosquito
@gnat: ¿Eso está mejor?
supercat
1
@gnat: Lo siento, un problema de red hizo que mi intento de publicar la edición fallara. ¿Te gusta más ahora?
supercat
-2

item.AddTo(items)no se usa porque es pasivo. Cf "El elemento se agrega a la lista" y "el decorador pinta la habitación".

Las construcciones pasivas están bien, pero, al menos en inglés, tendemos a preferir las construcciones activas.

En la programación OO, el paradigma le da protagonismo a los actores, no a los que actuaron, por lo que tenemos items.Add(item). La lista es lo que hace algo. Es el actor, así que esta es la forma más natural.

Matt Ellen
fuente
Depende de dónde esté parado ;-) - la teoría de la relatividad - podría decir lo mismo sobre list.Add(item) un elemento que se agrega a la lista o una lista agrega un elemento o sobre item.AddTo(list) el elemento se agrega a la lista o yo agrego el elemento a la lista o como has dicho, el elemento se agrega a la lista , todos son correctos. Entonces, la pregunta es quién es el actor y por qué. Un elemento también es un actor y quiere unirse a un grupo (lista), no el grupo quiere tener este elemento ;-) el elemento actúa activamente para estar en un grupo.
t3chb0t
El actor es lo que hace lo activo. "Querer" es una cosa pasiva. En la programación, es la lista la que cambia cuando se le agrega un elemento, el elemento no debería cambiar en absoluto.
Matt Ellen
... aunque a menudo le gusta cuando tiene una Parentpropiedad. Es cierto que el inglés no es el idioma nativo, pero por lo que sé, el deseo no es pasivo a menos que yo sea querido o él quiera que haga algo ; -]
t3chb0t
¿Qué acción realizas cuando quieres algo? Ese es mi punto. No es necesariamente gramaticalmente pasivo pero semánticamente lo es. Atributos primarios WRT, seguro que es una excepción, pero todas las heurísticas los tienen.
Matt Ellen
Pero List<T>(al menos el que se encuentra en System.Collections.Generic) no actualiza ninguna Parentpropiedad en absoluto. ¿Cómo podría, si se supone que es genérico? Por lo general, es responsabilidad del contenedor agregar su contenido.
Arturo Torres Sánchez