Je pense que la façon la plus courante d'ajouter quelque chose à une collection est d'utiliser une sorte de Add
méthode fournie par une collection:
class Item {}
var items = new List<Item>();
items.Add(new Item());
et il n'y a en fait rien d'inhabituel à cela.
Je me demande cependant pourquoi ne le faisons-nous pas de cette façon:
var item = new Item();
item.AddTo(items);
il semble que ce soit plus naturel que la première méthode. Cela aurait l'andvantange que lorsque la Item
classe a une propriété comme Parent
:
class Item
{
public object Parent { get; private set; }
}
vous pouvez rendre le setter privé. Dans ce cas, bien sûr, vous ne pouvez pas utiliser une méthode d'extension.
Mais peut-être que je me trompe et que je n'ai jamais vu ce modèle avant parce que c'est si rare? Savez-vous s'il existe un tel schéma?
Dans C#
une méthode d'extension serait utile pour cela
public static T AddTo(this T item, IList<T> list)
{
list.Add(item);
return item;
}
Et les autres langues? Je suppose que dans la plupart d'entre eux, la Item
classe devait fournir une ICollectionItem
interface appelons cela .
Update-1
J'y ai réfléchi un peu plus et ce modèle serait vraiment utile par exemple si vous ne voulez pas qu'un élément soit ajouté à plusieurs collections.
ICollectable
interface de 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);
}
et un exemple d'implémentation:
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
}
usage:
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);
item.AddTo(items)
supposons que vous ayez un langage sans méthodes d'extension: naturel ou non, pour prendre en charge addTo, chaque type aurait besoin de cette méthode et la fournirait pour chaque type de collection prenant en charge l'ajout. C'est comme le meilleur exemple d'introduction de dépendances entre tout ce que j'ai jamais entendu: P - Je pense que la fausse prémisse ici est d'essayer de modéliser une abstraction de programmation à la «vraie» vie. Cela tourne souvent mal.add(item, collection)
, mais ce n'est pas un bon style OO.Réponses:
Non,
item.AddTo(items)
ce n'est pas plus naturel. Je pense que vous mélangez cela avec les éléments suivants:Vous avez raison, ce
items.Add(item)
n'est pas très proche de la langue anglaise naturelle. Mais vous n'entendez pas non plusitem.AddTo(items)
en anglais naturel, n'est-ce pas ? Normalement, il y a quelqu'un qui est censé ajouter l'élément à la liste. Que ce soit lorsque vous travaillez dans un supermarché ou lorsque vous cuisinez et ajoutez des ingrédients.Dans le cas des langages de programmation, nous avons fait en sorte qu'une liste fasse les deux: stocker ses éléments et se charger de les ajouter à elle-même.
Le problème avec votre approche est qu'un élément doit être conscient qu'une liste existe. Mais un élément pourrait exister même s'il n'y avait pas de listes du tout, non? Cela montre qu'il ne doit pas être au courant des listes. Les listes ne doivent en aucun cas apparaître dans son code.
Les listes n'existent cependant pas sans éléments (au moins ils seraient inutiles). Par conséquent, c'est bien s'ils connaissent leurs articles - au moins sous une forme générique.
la source
@valenterry a tout à fait raison sur le problème de la séparation des préoccupations. Les classes ne doivent rien savoir des différentes collections qui pourraient les contenir. Vous ne voudriez pas avoir à modifier la classe d' élément chaque fois que vous créez un nouveau type de collection.
Cela dit...
1. Pas Java
Java ne vous aide pas ici, mais certains langages ont une syntaxe plus flexible et extensible.
Dans Scala, items.add (item) ressemble à ceci:
Où :: est un opérateur qui ajoute des éléments aux listes. Cela semble très bien ce que vous voulez. C'est en fait du sucre syntaxique pour
où :: est une méthode de liste Scala qui est effectivement équivalente à list.add de Java . Ainsi, alors qu'il semble dans la première version que l' élément s'ajoute aux éléments , en réalité, les éléments font l'ajout. Les deux versions sont valides Scala
Cette astuce se fait en deux étapes:
Si vous n'êtes pas à l'aise avec de nombreux opérateurs et que vous voulez votre addTo , Scala propose une autre option: les conversions implicites. Cela nécessite deux étapes
Si les conversions implicites sont activées et que Scala voit
ou
et l'élément n'a pas addTo , il recherchera dans la portée actuelle toutes les fonctions de conversion implicites. Si l' une des fonctions de conversion retourne un type qui fait avoir une addTo méthode, cela se produit:
Maintenant, vous pouvez trouver la route des conversions implicites plus à votre goût, mais cela ajoute du danger, car le code peut faire des choses inattendues si vous n'êtes pas clairement au courant de toutes les conversions implicites dans un contexte donné. C'est pourquoi cette fonctionnalité n'est plus activée par défaut dans Scala. (Cependant, bien que vous puissiez choisir de l'activer ou non dans votre propre code, vous ne pouvez rien faire concernant son statut dans les bibliothèques d'autres personnes. Voici des dragons).
Les opérateurs terminés par deux points sont plus laids mais ne constituent pas une menace.
Les deux approches préservent le principe de la séparation des préoccupations. Vous pouvez donner l' impression que l' élément connaît la collection, mais le travail se fait en fait ailleurs. Toute autre langue qui souhaite vous offrir cette fonctionnalité doit être au moins aussi prudente.
2. Java
En Java, où la syntaxe n'est pas extensible par vous, le mieux que vous puissiez faire est de créer une sorte de classe wrapper / décorateur qui connaît les listes. Dans le domaine où vos articles sont placés dans des listes, vous pouvez les envelopper. Quand ils quittent ce domaine, retirez-les. Peut-être que le modèle de visiteur est le plus proche.
3. tl; dr
Scala, comme certaines autres langues, peut vous aider avec une syntaxe plus naturelle. Java ne peut vous offrir que des motifs laids et baroques.
la source
Avec votre méthode d'extension, vous devez toujours appeler Add () afin de ne rien ajouter d'utile à votre code. À moins que votre méthode addto n'ait effectué un autre traitement, il n'y a aucune raison qu'elle existe. Et l'écriture de code qui n'a aucun but fonctionnel est mauvaise pour la lisibilité et la maintenabilité.
Si vous le laissez non générique, les problèmes deviennent encore plus apparents. Vous avez maintenant un élément qui doit connaître les différents types de collections dans lesquelles il peut se trouver. Cela viole le principe de responsabilité unique. Un élément est non seulement un support pour ses données, mais il manipule également les collections. C'est pourquoi nous laissons la responsabilité d'ajouter des éléments aux collections jusqu'au morceau de code qui les consomme tous les deux.
la source
thing.giveTo(collection)
[et nécessitant lacollection
mise en œuvre d'une interface "acceptOwnership"] aurait plus de sens que d'avoircollection
inclus une méthode qui a saisi la propriété. Bien sûr ...Je pense que vous êtes obsédé par le mot "ajouter". Essayez plutôt de rechercher un autre mot, tel que "collecter", qui vous donnerait une syntaxe plus conviviale en anglais sans changement de modèle:
La méthode ci-dessus est exactement la même que
items.add(item);
, avec la seule différence étant un choix de mots différent, et s'écoule très bien et "naturellement" en anglais.Parfois, le simple fait de renommer des variables, des méthodes, des classes, etc. empêchera la reconception fiscale du modèle.
la source
collect
est parfois utilisé comme nom pour les méthodes qui réduisent une collection d'éléments en une sorte de résultat singulier (qui peut également être une collection). Cette convention a été adoptée par l' API Java Steam , qui a rendu l'utilisation du nom dans ce contexte extrêmement populaire. Je n'ai jamais vu le mot utilisé dans le contexte que vous mentionnez dans votre réponse. Je préfère m'en tenir àadd
ouput
push
ouenqueue
. Le point n'est pas le nom d'exemple spécifique, mais le fait qu'un nom différent pourrait être plus proche du désir du PO pour une grammaire anglaise.Bien qu'il n'y ait pas un tel modèle pour le cas général (comme expliqué par d'autres), le contexte dans lequel un modèle similaire a ses avantages est une association un-à-plusieurs bidirectionnelle dans ORM.
Dans ce contexte, vous auriez deux entités, disons
Parent
etChild
. Un parent peut avoir plusieurs enfants, mais chaque enfant appartient à un parent. Si l'application nécessite que l'association soit navigable des deux côtés (bidirectionnelle), vous auriez unList<Child> children
dans la classe parent et unParent parent
dans la classe enfant.L'association doit toujours être réciproque bien sûr. Les solutions ORM appliquent généralement cela aux entités qu'elles chargent. Mais lorsque l'application manipule une entité (persistante ou nouvelle), elle doit appliquer les mêmes règles. D'après mon expérience, vous trouverez le plus souvent une
Parent.addChild(child)
méthode qui invoqueChild.setParent(parent)
, qui n'est pas à l'usage du public. Dans ce dernier, vous trouverez généralement les mêmes vérifications que celles que vous avez mises en œuvre dans votre exemple. L'autre itinéraire peut cependant aussi être implémenté:Child.addTo(parent)
qui appelleParent.addChild(child)
. Le meilleur itinéraire diffère d'une situation à l'autre.la source
En Java, une collection typique ne contient pas de choses - elle contient des références à des choses, ou plus précisément des copies de références à des choses. La syntaxe des membres de points, en revanche, est généralement utilisée pour agir sur la chose identifiée par une référence, plutôt que sur la référence elle-même.
Alors que placer une pomme dans un bol modifierait certains aspects de la pomme (par exemple, son emplacement), placer une feuille de papier qui dit "objet # 29521" dans un bol ne changerait en rien la pomme, même si elle se trouve être objet # 29521. De plus, parce que les collections contiennent des copies de références, il n'y a pas d'équivalent Java à placer un morceau de papier disant "objet # 29521" dans un bol. Au lieu de cela, on copie le numéro # 29521 sur une nouvelle feuille de papier et le montre (ne le donne pas) au bol, qui les fera faire sa propre copie, en laissant l'original inchangé.
En effet, étant donné que les références d'objets (les "bouts de papier") en Java peuvent être observées passivement, ni les références, ni les objets ainsi identifiés, n'ont de moyen de savoir ce qui en est fait. Il existe certains types de collections qui, au lieu de simplement contenir un tas de références à des objets qui peuvent ne rien savoir de l'existence de la collection, établissent plutôt une relation bidirectionnelle avec les objets qui y sont encapsulés. Avec certaines de ces collections, il peut être judicieux de demander à une méthode de s’ajouter à une collection (surtout si un objet ne peut se trouver que dans une seule collection à la fois). En général, cependant, la plupart des collections ne correspondent pas à ce modèle et peuvent accepter des références à des objets sans aucune implication de la part des objets identifiés par ces références.
la source
item.AddTo(items)
n'est pas utilisé car il est passif. Cf "L'article est ajouté à la liste" et "la pièce est peinte par le décorateur".Les constructions passives sont bien, mais, au moins en anglais, nous avons tendance à préférer les constructions actives.
Dans la programmation OO, le pardigme donne la prééminence aux acteurs, pas à ceux sur lesquels on a agi, donc nous l'avons fait
items.Add(item)
. La liste est la chose qui fait quelque chose. C'est l'acteur, c'est donc la voie la plus naturelle.la source
list.Add(item)
un élément est ajouté à la liste ou une liste ajoute un élément ou à propos deitem.AddTo(list)
l'élément s'ajoute à la liste ou j'ajoute l'élément à la liste ou comme vous l'avez dit, l'élément est ajouté à la liste - tous sont corrects. La question est donc de savoir qui est l'acteur et pourquoi? Un élément est également un acteur et il veut rejoindre un groupe (liste) et non le groupe veut avoir cet élément ;-) l'élément agit activement pour être dans un groupe.Parent
propriété. Certes, l'anglais n'est pas la langue maternelle, mais pour autant que je sache, vouloir n'est pas passif à moins que je ne veuille ou qu'il veuille que je fasse quelque chose ; -]List<T>
(au moins celui trouvé dansSystem.Collections.Generic
) ne met à jour aucuneParent
propriété. Comment pourrait-il, s'il est censé être générique? Il est généralement de la responsabilité du conteneur d'ajouter son contenu.