修改自身后,类的方法何时应返回相同的实例?


9

我有一个具有三个方法的类A()B()并且C()。这些方法修改了自己的实例。

当实例必须是一个单独的副本时,方法必须返回一个实例(与一样Clone()),但是在修改方法中的同一实例而不返回任何其他值时,我可以自由选择返回void还是相同的实例(return this;)。

在决定返回相同的修改实例时,我可以进行整洁的方法链,例如obj.A().B().C();

这是这样做的唯一理由吗?

修改自己的实例并返回它还可以吗?还是应该只返回副本并像以前一样保留原始对象?因为当返回相同的修改实例时,用户可能会假定返回的值是副本,否则将不会返回?如果可以的话,在方法中阐明此类问题的最佳方法是什么?

Answers:


7

何时使用链接

函数链接在带有自动完成功能的IDE较为普遍的语言中很普遍。例如,几乎所有的C#开发人员都使用Visual Studio。因此,如果您使用C#进行开发,则将链接添加到方法中可以为该类用户节省时间,因为Visual Studio将帮助您构建链接。

另一方面,本质上具有高度动态性且通常在IDE中通常不具有自动完成支持的语言(如PHP)将看到较少支持链接的类。仅当采用正确的phpDocs公开可链接方法时,链接才适用。

什么是连锁?

给定一个名为Foo的类,以下两个方法均可链接。

function what() { return this; }
function when() { return new Foo(this); }

一个是对当前实例的引用,而一个创建了一个新实例,这一事实并不能改变它们是可链接的方法。

没有黄金规则,可链接方法必须仅引用当前对象。实际上,可链接方法可以跨越两个不同的类。例如;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

this在以上任何示例中均未引用。该代码a.What().When()是链接的示例。有趣的是,类类型B从未分配给变量。

当方法的返回值用作表达式的下一个组成部分时,该方法将被链接。

这是更多示例

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

何时使用thisnew(this)

大多数语言中的字符串都是不可变的。因此,链接方法调用始终会导致创建新的字符串。其中可以修改StringBuilder之类的对象。

一致性是最佳实践。

如果您有修改对象状态并返回的this方法,请不要混入返回新实例的方法。而是创建一个特定的方法Clone(),该方法将显式地执行此操作。

 var x  = a.Foo().Boo().Clone().Foo();

关于内部发生的事情,这要清楚得多a

外面的一步和向后把戏

我将其称为“ 逐步退出”技巧,因为它解决了许多与链接有关的常见问题。从根本上讲,这意味着您从原始类中退出,进入新的临时类,然后返回原始类。

临时类的存在仅是为了向原始类提供特殊功能,而仅在特殊条件下才存在。

很多时候,一条链需要更改状态,但是类A不能代表所有这些可能的状态。因此,在链接过程中,引入了一个新类,其中包含对的引用A。这使程序员可以进入状态并返回A

这是我的示例,将特殊状态称为B

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

现在,这是一个非常简单的示例。如果您想拥有更多控制权,则B可以返回A具有不同方法的新超类型。


4
我真的不明白为什么您建议仅依靠类似于Intellisense的自动完成支持的方法链接。方法链或流利的接口是任何OOP语言的API设计模式。自动完成功能唯一要做的就是防止输入错误和类型错误。
阿蒙2014年

@amon你看错了我说的话。我说过,智能常用于一种语言时,它会更受欢迎。我从未说过这取决于功能。
Reactgular 2014年

3
虽然这个答案可以帮助我很多地了解链接的可能性,但是我仍然没有决定何时使用链接。当然,一致性是最好的。但是,我指的是在函数和中修改对象的情况return this。如何帮助图书馆的用户处理和了解这种情况?(即使不需要,也允许他们使用链式方法,这真的可以吗?或者我应该只坚持一种方法,根本不需要更改?)
Martin Braun 2014年

@modiX如果我的答案正确,那么请接受它作为答案。您正在寻找的其他内容是有关如何使用链接的意见,对此没有正确/错误的答案。这确实取决于您。也许您过于担心小细节?
Reactgular 2014年

3

这些方法修改自己的实例。

根据语言的不同,具有返回void/ unit修改其实例或参数的方法是惯用的。即使在以前做更多事情的语言(C#,C ++)中,随着向更多功能样式编程(不可变对象,纯函数)的转变,它也已过时。但是,让我们假设现在有充分的理由。

这是这样做的唯一理由吗?

对于某些行为(认为x++),即使该操作修改了变量,也期望该操作返回结果。但这很大程度上是您自己进行此操作的唯一原因。

修改自己的实例并返回它还可以吗?还是应该只返回副本并像以前一样保留原始对象?因为当返回相同的修改实例时,用户可能会承认返回的值是副本,否则将不会返回?如果可以的话,在方法中阐明此类问题的最佳方法是什么?

这取决于。

在复制/新建和返回很常见的语言(C#LINQ和字符串)中,返回相同的引用会造成混淆。在修改和返回很常见的语言(某些C ++库)中,复制会造成混乱。

使签名明确(通过返回void或使用诸如属性之类的语言构造)将是最好的澄清方法。之后,清楚SetFoo地显示名称以表明您正在修改现有实例是件好事。但是关键是要保持所使用语言/库的惯用语。


谢谢你的回答。我主要使用.NET框架,通过您的回答,它充满了非惯用语。诸如LINQ方法之类的东西在追加操作等之后返回原始的新副本,而诸如Clear()Add()任何集合类型的之类的东西将修改同一实例并返回void。在两种情况下,您都说这会令人困惑……
Martin Braun

@modix - LINQ并没有同时追加操作返回副本。
Telastyn 2014年

为什么我能做到obj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);呢?Where()返回一个新的集合对象。OrderBy()甚至返回另一个收集对象,因为内容是有序的。
马丁·布劳恩

1
@modix -他们返回实现新的目标IEnumerable,但要清楚,他们不是副本,它们不是集合(除ToListToArray等等)。
Telastyn

是的,它们不是副本,而且它们确实是新对象。同样,通过说集合,我指的是可以计数的对象(在任何类型的数组中包含多个对象的对象),而不是从继承的类ICollection。我的错。
马丁·布劳恩

0

(我以C ++为您的编程语言)

对我来说,这主要是可读性方面。如果A,B,C是修饰符,特别是如果这是作为参数传递给某个函数的临时对象,例如

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

相比

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

如果可以返回对修改后的实例的引用,可以重新编译,我会说是的,并例如将您指向流运算符'>>'和'<<'(http://www.cprogramming.com/tutorial/ operator_overloading.html


1
您认为错了,这是C#。但是我希望我的问题更普遍。
2014年

当然,也可以是Java,但这只是将我的示例与运算符放到上下文中的一个小问题。其实质是归结为可读性,这受读者的期望和背景的影响,而这些期望和背景又受一种正在处理的特定语言/框架中的惯常做法的影响-其他答案也指出了这一点。
AdrianI 2014年

0

您也可以使用复制返回来进行方法链接,而不是修改返回。

一个很好的C#示例是string.Replace(a,b),它不会更改调用它的字符串,而是返回一个新字符串,使您可以轻松地链接在一起。


是的我知道。但是我不想提及这种情况,因为无论如何我都不想编辑自己的实例时就不得不使用这种情况。但是,感谢您指出这一点。
马丁·布劳恩

对于其他答案,语义的一致性很重要。由于您可以与复制返回保持一致,而与修改返回不能保持一致(因为您不能对不可变的对象执行此操作),因此我想说您问题的答案是不使用复制返回。
Peter Wone 2014年
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.