Creo que la forma más común de agregar algo a una colección es usar algún tipo de Add
mé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 Item
clase 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 Item
clase tenía que proporcionar una ICollectionItem
interfaz 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.
ICollectable
interfaz 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);
fuente
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.add(item, collection)
, pero ese no es un buen estilo OO.Respuestas:
No,
item.AddTo(items)
no es más natural. Creo que mezclas esto con lo siguiente:Tienes razón en que
items.Add(item)
no está muy cerca del idioma inglés natural. Pero tampoco escuchasitem.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.
fuente
@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í:
Where :: es un operador que agrega elementos a las listas. Eso parece mucho lo que quieres. En realidad es azúcar sintáctica para
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:
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
Si las conversiones implícitas están habilitadas y Scala ve
o
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:
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.
fuente
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.
fuente
thing.giveTo(collection)
[y requerir lacollection
implementación de una interfaz "acceptOwnership"] tendría más sentido quecollection
incluir un método que tomara posesión. Por supuesto ...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:
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.
fuente
collect
a 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 conadd
oput
push
oenqueue
. 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.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
Parent
yChild
. 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 unaList<Child> children
en la clase principal y unaParent parent
en 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 invocaChild.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é llamadasParent.addChild(child)
. La ruta que mejor difiere de una situación a otra.fuente
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.
fuente
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.fuente
list.Add(item)
un elemento que se agrega a la lista o una lista agrega un elemento o sobreitem.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.Parent
propiedad. 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 ; -]List<T>
(al menos el que se encuentra enSystem.Collections.Generic
) no actualiza ningunaParent
propiedad en absoluto. ¿Cómo podría, si se supone que es genérico? Por lo general, es responsabilidad del contenedor agregar su contenido.