方法链的优点和缺点以及由对象本身替换所有void返回参数的可能性

Mar*_*rga 22 java design-patterns chaining

我最感兴趣的是Java,但我认为这是一个普遍的问题.最近我一直在使用Arquillian framework(ShrinkWrap),它使用了很多方法链接.方法链接的其他实例是在方法StringBuilder,StringBuffer.使用这种方法有明显的好处:减少详细程度就是其中之一.

现在我想知道,为什么并非所有将void返回参数实现为可链接的方法?链接必然存在一些明显和客观的缺点.因为如果所有方法都是可链接的,我仍然可以选择不使用它.

我不是要求改变Java中的现有代码,这可能会破坏某些地方的某些东西,但解释为什么不使用它也会很好.我更多地要求从未来的框架(用Java编写)设计视角.


我发现了一个类似的问题,但原来的提问者实际上想知道为什么它被认为是一种好的做法:方法链 - 为什么这是一个好的做法,或不是?


虽然有一些答案可用,但我仍然不确定链接的所有优点和缺点是什么,以及将所有void方法链接起来是否有用会被认为是有用的.

Ric*_*gle 24

缺点

  • 原则上它会混淆签名,如果某些东西返回一个新实例,我不认为它也是一个mutator方法.例如,如果一个向量有一个缩放方法,那么如果它有一个返回,我会假设它返回一个由输入缩放的新向量,如果没有那么我会期望它在内部缩放.
  • 当然,如果扩展了类,那么你会遇到问题,在你的对象链接中途会被转换成超类型.在父类中声明链接方法但在子类的实例上使用时会发生这种情况.

优点

  • 它允许将数学方程式样式代码写成完整的方程式,而不需要多个中间对象(导致不必要的开销),例如,如果没有方法链接,矢量三重交叉乘积(作为随机示例)将必须写为

    MyVector3d tripleCrossProduct=(vector1.multiply(vector2)).multiply(vector3);
    
    Run Code Online (Sandbox Code Playgroud)

    它的缺点是创建一个必须创建和垃圾收集的中间对象,或者

    MyVector3d tripleCrossProduct=vector1;
    tripleCrossProduct.multiplyLocal(vec2);
    tripleCrossProduct.multiplyLocal(vec3);
    
    Run Code Online (Sandbox Code Playgroud)

    这避免了中间对象的创建但是很不清楚,变量名tripleCrossProduct实际上是直到第3行的谎言.但是,如果你有方法链接,这可以用正常的数学方式简洁地编写,而不会创建不必要的中间对象.

    MyVector3d tripleCrossProduct=vector1.multiplyLocal(vector2).multiplyLocal(vector3);
    
    Run Code Online (Sandbox Code Playgroud)

    所有这一切都假设vector1是牺牲性的,永远不需要再次使用

  • 当然还有明显的好处; 简洁.即使您的操作没有链接到我上面示例的庄园中,您仍然可以避免对该对象的不必要的引用

    SomeObject someObject=new SomeObject();
    someObject
      .someOperation()
      .someOtherOperation();
    
    Run Code Online (Sandbox Code Playgroud)

NB MyVector3d不是用作真正的Java类,而是假设.multiply()在调用方法时执行交叉产品..cross()没有使用,以便那些不熟悉矢量微积分的人的'意图'更清楚
NB Amit的解决方案是使用多线方法链的第一个答案,我把它作为完整性的第四个要点的一部分包括在内


Ten*_*giz 19

方法链接是一种实现流畅接口的方法,无论编程语言如何.它的主要好处(可读代码)告诉您何时使用它.如果对可读代码没有特别需要,最好避免使用它,除非API自然地设计为由于方法调用而返回上下文/对象.

第1步:Fluent界面与Command-Query API

必须针对命令查询API考虑Fluent接口.为了更好地理解它,让我在下面编写命令查询API的子弹列表定义.简单来说,这只是一种标准的面向对象编码方法:

  • 修改数据的方法称为a Command.命令不返回值.
  • 返回值的方法称为a Query.查询不会修改数据.

遵循命令查询API将为您带来以下好处:

  • 查看面向对象的代码,您可以了解正在发生的事情.
  • 调试代码更容易,因为每次调用都是单独进行的.

步骤2:在Command-Query API之上的Fluent接口

但是命令查询API由于某种原因而存在,而且确实读得更好.那么我们如何获得流畅的界面和命令查询API的好处呢?

答:必须在命令查询API之上实现流畅的接口(而不是通过流畅的接口替换命令查询API ).将流畅的界面视为命令查询API的外观.毕竟,它被称为流畅的" 界面 " - 一个比标准(命令查询)API更可读或更方便的界面.

通常,在命令查询API准备好(写入,可能经过单元测试,抛光到易于调试)之后,您可以在其上编写流畅的接口软件层.换句话说,流畅的界面通过使用命令查询API来完成其功能.然后,使用流畅的界面(使用方法链接),只要您想要方便和可读性.但是,一旦您想了解实际发生的事情(例如,在调试异常时),您就可以随时深入了解命令查询API - 良好的旧的面向对象代码.

  • +1 这听起来是框架设计的好主意。我希望你能得到一些支持,这样我就不是唯一喜欢它的人了。 (4认同)

Har*_*mar 7

我发现使用方法链接的缺点是在发生NullPointerException任何其他Exception情况时调试代码.假设您有以下代码:

String test = "TestMethodChain"; test.substring(0,10).charAt(11); //This is just an example

然后你会得到String index超出范围:执行上面代码时的异常.当你进入实时情况并且发生这样的事情时,你会得到发生了哪一行错误但是没有链接方法的哪一部分引起它.因此,需要明智地使用它,因为它知道数据将始终存在或错误得到正确处理.

它有它的优点,就像你不需要编写多行代码和使用多个变量.

许多框架/工具像Dozer一样使用它,当我用来调试代码时,我必须查看链的每个部分以找出导致错误的原因.

希望这可以帮助.

  • 通常,您可以告诉调试器停止异常并向您显示堆栈跟踪.当我在Intellij IDEA中调试你的例子时,它直接转到String.charAt(11). (3认同)

Nit*_*thi 5

您可能想阅读Martin Fowler的Fluent Interface

总结

  • 不要对方法进行连锁,主要是因为它违反了命令查询责任隔离(CQRS)设计原则。
  • 通过使其更接近于企业谈论这些操作,将其视为内部DSL的方式来改进api设计
  • 尝试避免链接独立的方法,因为它们会污染API,并且可能无法向代码的客户端/维护者揭示意图。