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

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

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

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


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


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

缺点

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

优点

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

     MyVector3d tripleCrossProduct=(vector1.multiply(vector2)).multiply(vector3); 

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

     MyVector3d tripleCrossProduct=vector1; tripleCrossProduct.multiplyLocal(vec2); tripleCrossProduct.multiplyLocal(vec3); 

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

     MyVector3d tripleCrossProduct=vector1.multiplyLocal(vector2).multiplyLocal(vector3); 

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

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

     SomeObject someObject=new SomeObject(); someObject .someOperation() .someOtherOperation(); 

注意: MyVector3d没有被用作真正的Java类,但是当.multiply()方法时,它被假定为执行交叉产品。 不使用.cross() ,以便那些不熟悉矢量微积分的人更清楚’意图’
NB Amit的解决方案是使用多线方法链接的第一个答案,我将其作为第四个完整性要点的一部分包含在内

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

希望这可以帮助。

您可能希望阅读Martin Fowler关于Fluent Interface的内容

概略地

  • 不要链接方法主要是因为它违反了Command Query Responsibility Segregation(CQRS)设计原则。
  • 通过使api设计更接近业务谈论这些操作的方式来改进api设计,将它们视为内部DSL
  • 尝试避免链接独立方法因为它们污染API并且可能无法向代码的客户端/维护者泄露意图

当需要对同一对象进行一系列更新并且更新操作不需要返回任何更新状态时,此模式很有用。 例如,我在为数据库层编写的一些api中使用了这种模式。 为了根据许多条件获取某些行,需要将许多条件添加到where子句中。 使用此模式,可以按如下方式添加标准。

 CriteriaCollection().instance() .addSelect(Criteria.equalTo(XyzCriteria.COLUMN_1, value1)) .addSelect(Criteria.equalTo(XyzCriteria.COLUMN_2, value2)) .addSelect(Criteria.isIn(XyzCriteria.COLUMN_3, values3)) .addOrder(OrderCriteria.desc(XyzCriteria.Order.COLUMN_1)); 

最终它提高了代码的可读性。

如果您更喜欢不变性函数式编程 ,那么您永远不会返回void

没有返回值的函数仅被调用其副作用

当然,有些情况不适用,但返回void的函数可以被视为以不同方式尝试它的提示。

我个人认为这是一个非常有用的模式,但它不应该在任何地方使用。 考虑一下你有copyTo(T other)方法的情况。 通常你会期望它不会返回任何东西,但是如果它返回一个相同类型的对象,你会期望哪个对象? 这类问题可以通过文档清除,但在方法签名上仍然含糊不清。

 public class MyObject { // ... my data public MyObject copyTo(MyObject other) { //... copy data // what should I return? return this; } } 

这是一项相当多的工作。 特别是涉及inheritance时。 看看这篇关于构建器模式的精彩文章

当你期望客户端会在同一个实例上顺序调用多个方法时,例如构建器模式,不可变对象等,实现它是很有意义的。但在我看来,在大多数情况下它并不值得额外的努力。

对象具有属性和方法。 每种方法都实现了对象总体目的的一部分。 某些类型的方法(如构造函数和getter和setter)正在执行属性和对象本身的生命周期管理。 其他方法返回对象的状态及其属性。 这些方法通常是无效的。

Void方法有两种forms:1。关于对象的生命周期管理或属性。 2.具有在该方法内完全处理的输出,并且不应在其他任何地方引起任何改变状态。

广告1.它们属于对象的内部工作。 ad 2.参数信息用于执行方法中的一些工作。 方法完成后,对象本身的效果和其中一个参数的内部状态没有变化。

但是为什么要让一个方法返回void,为什么不用例如布尔值(成功与否)? 返回void的方法(如在setter中)的动机是该方法没有副作用。 返回真或假可能是副作用。 Void意味着当方法按设计执行时,该方法没有副作用。 返回exception的void方法很好,因为exception不是方法正常处理的副作用。

返回void的方法意味着它根据定义是一种孤立的工作方法。 一系列void方法是一系列松散耦合的方法,因为没有其他方法可以依赖其前任的结果,因为没有方法有任何结果。 这有一种设计模式,即责任链设计模式。 链接不同的记录器是一个传统的例子,并且调用servlet api中的后续filter。

以有意义的方式链接void方法意味着这些方法在其上工作的共享对象是在一个状态的每个步骤之后,可以通过处理该对象的void方法理解。 所有后续呼叫可能不依赖于其前任的结果,也不会影响呼叫后的呼叫工作。 最简单的方法是确保不改变对象的内部状态(如记录器示例),或让每个方法更改对象内部状态的另一部分。

在我看来,要链接的无效方法只有那些具有风味2并共享某种处理类型的方法。 我不会链接关于类的生命周期的void方法,因为每个方法都是对象整个状态的不同部分的更改。 随着时间的推移,这些方法的存在会随着类的设计的任何变化而改变,因此我不建议将这些方法链接起来。 此外,第一种类型的void方法抛出的所有exception都是彼此独立的,而您可能期望由风格2的链式void方法抛出的exception共享某种类型的处理有一些共同点。