是否有一种模式可以更自然地向收藏夹添加项目?[关闭]


13

我认为向集合中添加某些内容的最常见方法是使用Add集合提供的某种方法:

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

实际上,这没有什么不寻常的。

我不知道为什么我们不这样做:

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

它似乎比第一种方法更自然。当Item类具有类似Parent以下属性时,将具有andvantange :

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

您可以将二传手设为私人。在这种情况下,您当然不能使用扩展方法。

但是也许我错了,我以前从未见过这种模式,因为它是如此罕见?你知道有没有这种模式?

C#扩展方法中将对此有用

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

其他语言呢?我猜大多数情况下,Item该类必须提供一个名为“ ICollectionItem接口”的接口。


更新1

我一直在考虑这一点,例如,如果您不希望将某个项目添加到多个集合中,那么这种模式将非常有用。

测试ICollectable界面:

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

和示例实现:

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
}

用法:

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

13
item.AddTo(items)假设您有一门没有扩展方法的语言:无论是否自然,要支持addTo,每种类型都需要此方法,并为支持追加的每种类型的集合提供该方法。这就像在我所听到的所有内容之间引入依赖关系的最佳示例:P-我认为这里的错误前提是试图对“现实”生活中的某些编程抽象进行建模。这经常出错。
stijn 2015年

1
@ t3chb0t呵呵,很好。我想知道您的第一语言是否会影响看起来更自然的结构。在我看来,唯一看起来很自然的就是add(item, collection),但这不是好的OO风格。
Kilian Foth,2015年

3
@ t3chb0t在口语中,您可以正常或反身地阅读内容。“约翰,请把这个苹果加到你随身携带的东西里。” 与“应该将苹果添加到您所携带的东西中,约翰。” 在OOP的世界中,反身没有多大意义。一般来说,您不会要求某些东西会受到另一个对象的影响。OOP中与此最接近的是访客模式。有一个原因,尽管这不是常态。
尼尔


3
@ t3chb0t您可能会发现有趣的阅​​读《名词王国》
保罗

Answers:


51

不,item.AddTo(items)这不自然。我认为您将此与以下内容混淆:

t3chb0t.Add(item).To(items)

您说得对,因为items.Add(item)它与自然英语不是很接近。但是您也听不到item.AddTo(items)自然的英语,对吗?通常,应该有人将项目添加到列表中。在超市工作或烹饪和添加配料时都可以。

在使用编程语言的情况下,我们做到了这样一个列表可以做到:既存储项目,负责将其添加到自身。

您的方法存在的问题是,项目必须知道列表存在。但是,即使根本没有列表,项目也可能存在,对吗?这表明它不应该知道列表。列表不应该以任何方式出现在其代码中。

但是,列表没有项目就不存在(至少它们是无用的)。因此,如果他们知道自己的物品,那至少可以是通用形式。


4

@valenterry关于关注点分离问题是正确的。类可能对可能包含它们的各种集合一无所知。每次创建新的收藏集时,您都不需要更改项目类。

那就是...

1.不是Java

Java在这里对您没有帮助,但是某些语言具有更灵活,可扩展的语法。

在Scala中,items.add(item)看起来像这样:

  item :: items

其中::是一个将项目添加到列表的运算符。这似乎是您想要的。它实际上是语法糖

  items.::(item)

其中::是Scala列表方法,实际上等效于Java的list.add。因此,尽管在第一个版本中看起来好像item正在将自身添加到item中,但实际上item正在添加。这两个版本都是有效的Scala

此技巧分为两个步骤:

  1. 在Scala中,运算符实际上只是方法。如果将Java列表导入Scala,则 items.add(item)也可以写成items add item。在Scala中,1 + 2实际上是1。+(2)。在带有空格而不是点和方括号的版本中,Scala看到第一个对象,寻找与下一个单词匹配的方法,然后(如果该方法带有参数)将下一个事物传递给该方法。
  2. 在Scala中,以冒号结尾的运算符是右关联的,而不是左的。因此,当Scala看到该方法时,它将在右侧寻找方法所有者,并在左侧输入值。

如果您对许多运算符不满意并且想要addTo,Scala提供了另一个选择:隐式转换。这需要两个步骤

  1. 创建一个名为ListItem的包装器类,该包装器类在其构造函数中使用一个项目,并具有一个addTo方法,该方法可以将该项目添加到给定列表中。
  2. 创建一个将项目作为参数并返回包含该项目ListItem的函数。将其标记为隐式

如果启用了隐式转换并且Scala看到

  item addTo items

要么

  item.addTo(items)

并且item没有addTo,它将在当前范围内搜索任何隐式转换函数。如果任何转换函数返回的类型确实具有addTo方法,则会发生这种情况:

  ListItem.(item).addTo(items)

现在,您可能会发现隐式转换更适合您的口味,但这会增加危险,因为如果您在给定的上下文中不清楚所有隐式转换,则代码可能会执行意外的操作。这就是为什么默认情况下在Scala中不再启用此功能的原因。(但是,尽管您可以选择是否在自己的代码中启用它,但是您不能在其他人的库中对其进行任何操作。这是一条龙。)

以冒号结尾的操作员比较丑陋,但不构成威胁。

两种方法都保留了关注点分离的原则。您可以使其看起来好像项目了解集合,但实际上是在其他地方完成的。任何其他想要为您提供此功能的语言都应至少小心。

2. Java

在Java中,语法不能由您扩展,因此您最好的办法是创建某种知道列表的包装器/装饰器类。在将项目放置在列表中的域中,可以将它们包装在其中。当他们离开该域时,将其带出。也许访客模式是最接近的。

3. tl; dr

与其他语言一样,Scala可以帮助您使用更自然的语法。Java只能为您提供难看的巴洛克式模式。


2

使用扩展方法,您仍然必须调用Add(),这样它就不会对您的代码添加任何有用的东西。除非您的addto方法进行了其他处理,否则没有理由存在它。编写没有功能目的的代码不利于可读性和可维护性。

如果您将其保留为非通用,则问题将变得更加明显。现在,您有一个项目需要了解其可能属于的不同种类的集合。这违反了单一责任原则。一个项目不仅是其数据的持有者,而且还可以操纵集合。这就是为什么我们要负责将项目添加到集合中,直到使用这两个代码的代码段为止。


如果框架要识别不同类型的对象引用(例如,区分封装所有权的对象引用和不封装所有权的对象引用),那么有一种方法可以使封装所有权的引用可以将所有权放弃给一个能够接收它。如果每件事仅限于拥有一个所有者,那么通过thing.giveTo(collection)[并要求collection实现“ acceptOwnership”接口] 转移所有权,比collection包括获取所有权的方法更有意义。当然...
超级猫

... Java中的大多数东西都没有所有权的概念,对象引用可以存储到集合中,而不会涉及由此确定的对象。
超级猫

1

我认为您沉迷于“添加”一词。尝试寻找替代单词,例如“ collect”,这将为您提供更加英语友好的语法,且模式不变:

items.collect(item);

上面的方法与完全相同items.add(item);,唯一的区别是选择了不同的单词,并且英语的流动性非常好且“自然”。

有时,仅重命名变量,方法,类等将阻止繁琐的模式重新设计。


collect有时用作将项目集合简化为某种形式的奇异结果(也可以是集合)的方法的名称。Java Steam API采纳了此约定,该约定在这种情况下极大地普及了该名称的用法。我从未见过您在答案中提到的上下文中使用的单词。我宁愿坚持addput
toniedzwiedz

@toniedzwiedz,所以考虑pushenqueue。关键不是具体的示例名称,而是一个不同的名称可能更接近OP对英语式语法的渴望的事实。
Brian S

1

尽管一般情况下没有这样的模式(如其他人所解释的),但相似模式确实有其优点的上下文是ORM中的双向一对多关联。

在这种情况下,您将有两个实体,例如ParentChild。一位父母可以有多个孩子,但每个孩子都属于一个父母。如果应用程序要求关联可以从两端进行导航(双向),则List<Child> children在父类中将有一个,在子类中将有一个Parent parent

当然,这种联系应该总是互惠的。ORM解决方案通常在加载的实体上实施此操作。但是,当应用程序正在操纵一个实体(持久的或新的)时,它必须执行相同的规则。以我的经验,您通常会发现一个Parent.addChild(child)调用的方法Child.setParent(parent),该方法不供公共使用。在后者中,您通常会发现与示例中相同的检查。但是,也可以实现另一条路线:Child.addTo(parent)调用Parent.addChild(child)。哪条路线最好因情况而异。


1

在Java中,典型的集合不包含事物 -它包含对事物的引用,或更准确地说,是对事物的引用的副本。相比之下,点成员语法通常用于对引用所标识的事物进行操作,而不是对引用本身进行操作。

虽然将苹果放在碗中会改变苹果的某些方面(例如其位置),但是将纸条上写有“对象#29521”的纸条装进碗中,即使碰巧是对象#29521 此外,由于集合包含引用的副本,因此没有Java等效于将纸条上写着“对象#29521”放入碗中。取而代之的是,将数字#29521复制到一张新的纸条上,并显示(而不是提供)到碗上,由他们自己制作副本,而原件不受影响。

确实,由于可以被动地观察Java中的对象引用(“纸条”),因此这些引用以及由此标识的对象都无法以任何方式知道对它们的处理方式。有一些类型的集合,而不是仅持有可能不知道集合存在的对象的一堆引用,而是与封装在其中的对象建立了双向关系。对于某些此类集合,可能有必要提出一种将自身添加到集合中的方法(特别是如果某个对象一次只能位于一个这样的集合中)。但是,一般而言,大多数集合都不适合该模式,并且可以接受对对象的引用,而这些引用所标识的对象的任何部分都不会涉及任何对象。


这篇文章很难阅读(文字墙)。您介意将其编辑为更好的形状吗?
蚊蚋

@gnat:更好吗?
超级猫

1
@gnat:很抱歉-网络故障导致我发布编辑的尝试失败。您现在喜欢它更好吗?
超级猫

-2

item.AddTo(items)不使用,因为它是被动的。cf“该项目已添加到列表”和“房间由装饰员绘制”。

被动构造很好,但至少在英语中,我们倾向于主动构造。

在OO编程中,范式突出了参与者,而不是被参与者,所以我们有了items.Add(item)。清单是做某事的事情。它是演员,所以这是更自然的方式。


这取决于你所站;-) -相对论-你能说同样的list.Add(item) 一个项目被添加到列表列表中增加了一个项目或有关item.AddTo(list) 项目将自身添加到列表我添加的项目到列表中,或者就像您已经说过一样将商品添加到列表中 -所有这些都是正确的。那么问题是谁是演员,为什么?一个项目也是一个演员,它想加入一个小组(列表),而不是该小组希望拥有这个项目;-)该项目积极地参与到小组中。
t3chb0t

演员是做活跃的事情。“想要”是一个被动的东西。在编程中,列表是在将项目添加到其中时更改的,该项目完全不应更改。
马特·艾伦

...尽管它经常像拥有Parent财产一样。诚然,英语不是母语,但据我所知,除非我想要他要我做点什么,否则想要不是消极的;-]
t3chb0t 2015年

您想要什么时会执行什么动作?这就是我的意思。它在语法上不一定是被动的,但在语义上却是被动的。WRT父属性,请确保这是一个例外,但是所有启发式方法都具有它们。
马特·艾伦

但是List<T>(至少在中找到的System.Collections.Generic)根本不会更新任何Parent属性。如果应该是通用的,怎么可能?容器通常负责添加其内容。
Arturo TorresSánchez2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.