¿Existe un patrón para una forma más "natural" de agregar artículos 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 método Add que proporciona 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 la ventaja de que cuando la clase Item 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 quizás me equivoque y nunca haya visto este patrón antes porque es tan poco común? ¿Sabe si existe algún patrón de este tipo?

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é hay de otros idiomas? Supongo que en la mayoría de ellos la clase Item tuvo que proporcionar una interfaz que llamamos ICollectionItem .

Update-1

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

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

y una implementación de ejemplo:

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

7 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 el idioma inglés natural, ¿verdad? Normalmente hay alguien que se supone que debe agregar el elemento a la lista. Ya sea al trabajar en un supermercado o al cocinar y agregar ingredientes.

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

El problema con su enfoque es que un elemento debe tener en cuenta que existe una lista. Pero un artículo podría existir incluso si no hubiera listas, ¿verdad? Esto demuestra que no debe ser consciente de las listas. Las listas no deberían 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.

    
respondido por el valenterry 15.01.2015 - 09:46
4

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

Dicho esto ...

1. No Java

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

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

  item :: items

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

  items.::(item)

donde :: es un método de lista de Scala que es efectivamente equivalente a list.add de Java. Entonces, mientras se ve en la primera versión como si el elemento se agregara a los elementos , en verdad, los elementos están haciendo el agregado. 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 de Java en Scala, items.add (item) también puede escribirse items add item . 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 el propietario del método a la derecha y alimenta el valor de la izquierda.

Si no te sientes cómodo con muchos operadores y quieres tu addTo , Scala ofrece otra opción: conversiones implícitas. Esto requiere dos pasos

  1. Cree una clase contenedora llamada, por ejemplo, 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 . Marque 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 alcance actual cualquier función de conversión implícita. Si alguna de las funciones de conversión devuelve un tipo que tiene tiene un método addTo , esto sucede:

  ListItem.(item).addTo(items)

Ahora, puede encontrar la ruta de las conversiones implícitas más a su gusto, pero agrega peligro, porque el código puede hacer cosas inesperadas si no está claramente al tanto de todas las conversiones implícitas en un contexto dado. Por esta razón, esta función ya no está habilitada de forma predeterminada en Scala. (Sin embargo, si bien puede elegir si desea habilitarlo o no en su propio código, no puede hacer nada acerca de su estado en las bibliotecas de otras personas. Aquí puede encontrar 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 se vea como si item sepa acerca de la colección, pero el trabajo en realidad se realiza en otro lugar. Cualquier otro idioma que quiera proporcionarle esta función debe ser al menos tan cuidadoso.

2. Java

En Java, donde usted no puede ampliar la sintaxis, lo mejor que puede hacer es crear algún tipo de envoltorio / decorador de clase que sepa acerca de las listas. En el dominio donde se colocan sus artículos en las listas, podría incluirlos en esa lista. Cuando dejen ese dominio, sácalos. Quizás el patrón visitante sea 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.

    
respondido por el itsbruce 15.01.2015 - 15:50
2

Con su método de extensión, aún tiene que llamar a Add () para que no agregue nada útil a su código. A menos que su método addto haya hecho 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 el mantenimiento.

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 se puede encontrar. 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 fragmento de código que los consume a ambos.

    
respondido por el control 15.01.2015 - 09:51
1

Creo que estás obsesionado con la palabra "agregar". En su lugar, intente buscar una palabra alternativa, como "recopilar", 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 que items.add(item); , con la única diferencia de ser una opción de palabra diferente, y fluye muy bien y "naturalmente" en inglés.

A veces, simplemente cambiar el nombre de las variables, métodos, clases, etc. evitará que se vuelva a diseñar el patrón.

    
respondido por el RestInPeace 15.01.2015 - 17:07
0

Aunque no existe tal patrón para el caso general (como se explica por 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ías dos entidades, digamos Parent y Child . Un padre puede tener muchos hijos, pero cada hijo pertenece a un padre. Si la aplicación requiere que la asociación sea navegable desde ambos extremos (bidireccional), tendría un List<Child> children en la clase principal y un Parent parent en la clase secundaria.

La asociación siempre debe ser recíproca, por supuesto. Las soluciones de 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 hacer cumplir las mismas reglas. En mi experiencia, la mayoría de las veces encuentras un método Parent.addChild(child) que invoca a 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, la otra ruta también se puede implementar: Child.addTo(parent) , que llama a Parent.addChild(child) . Qué ruta es mejor difiere de una situación a otra.

    
respondido por el Maarten Winkels 15.01.2015 - 22:56
0

En Java, una colección típica no contiene cosas --tiene referencias a cosas, o más precisamente copias de referencias a las cosas. La sintaxis de los miembros de puntos, por el contrario, se usa generalmente para actuar sobre la cosa identificada por una referencia, en lugar de sobre la referencia misma.

Al colocar una manzana en un tazón alteraría algunos aspectos de la manzana (por ejemplo, su ubicación), colocar una hoja de papel que diga "objeto # 29521" en un tazón no cambiaría la manzana de ninguna manera, incluso si pasa a ser objeto # 29521. Además, debido a que las colecciones contienen copias de referencias, no hay un equivalente de Java a colocar una hoja de papel que diga "objeto # 29521" en un recipiente. En su lugar, uno copia el número # 29521 en una nueva hoja de papel, y muestra (no le da) eso al tazón, que hará su propia copia, sin afectar al original.

De hecho, dado que las referencias a objetos (las "hojas de papel") en Java pueden observarse de manera pasiva, ni las referencias, ni los objetos identificados de este modo, tienen forma de saber qué se está haciendo con ellas. Existen algunos tipos de colecciones que, en lugar de limitarse a mantener 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 pedir un método para agregarse 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 ningún tipo de participación por parte de los objetos identificados por esas referencias.

    
respondido por el supercat 15.01.2015 - 17:33
-2

item.AddTo(items) no se usa porque es pasivo. C.f. "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 construcciones activas.

En la programación de OO, el pardigm otorga importancia a los actores, no a los que se aplican, por lo que tenemos items.Add(item) . La lista es la cosa que hace algo. Es el actor, así que esta es la forma más natural.

    
respondido por el Matt Ellen 15.01.2015 - 09:52

Lea otras preguntas en las etiquetas