java - 如何保证方法调用中的可交换性?
问题描述
我正在使用Ward Cunningham 的 Exceptional Value 和 Meaningless Behavior patterns的组合来尝试无例外地编程的想法。
假设我有一个接口 Fraction,旨在表示可以通过将 2 个其他量除以一组操作来表示的任何量:
interface Fraction {
Fraction add(Fraction f);
Fraction subtract(Fraction f);
Fraction multiply(Fraction f);
Fraction divide(Fraction f);
}
现在,该接口将有 2 个主要实现: a) SimpleFraction,其中分母不为 0;b) UndefinedFraction,其中分母为 0。两者都将由工厂创建:
public final class SimpleFraction implements Fraction {
private final int numerator, denominator;
private SimpleFraction(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
// interface method implementations
}
public final class UndefinedFraction implements Fraction {
private final int numerator;
private UndefinedFraction(int numerator) {
this.numerator = numerator;
}
// interface method implementations
}
UndefinedFraction 将是一个具有无意义行为的异常值:它的每个操作都应该返回一个 UndefinedFraction。更重要的是,如果它在任何其他分数的运算中用作参数,结果也应该是一个 UndefinedFraction :
FractionFactory factory = new FractionFactory();
Fraction simple = factory.fromNumeratorAndDenominator(420, 10);
Fraction undefined = factory.fromNumeratorAndDenominator(42, 0);
assertEquals(simple.add(undefined), undefined);
assertEquals(undefined.add(simple), undefined);
assertEquals(undefined.add(undefined), undefined);
如何在涉及 UndefinedFraction 的操作中强制执行这种交换性?我正在寻找基于多态性的选项(例如,它们不应该依赖于布尔检查)。
解决方案
问题是关联性。
术语:给定'a / b',我们称a为LHS(左手边),b为RHS,'/'为运算符,'divide'运算。LHS 和 RHS 都是“操作数”。
问题:假设我们创建了两种未定义的风格。我们有“红色未定义”和“绿色未定义”,我们坚持相同的定义:LHS 和/或 RHS 为“红色未定义”的任何操作都应生成“红色未定义”。
相同的规则适用于“绿色未定义”:LHS 和/或 RHS 为“绿色未定义”的任何操作都应为“绿色未定义”,而不管其他操作数的值和操作如何。
不幸的是,这个定义是模棱两可的:
Fraction red = factory.newRedUndefined();
Fraction green = factory.newGreenUndefined();
Fraction whatColorIsThis = red.add(green);
那应该是红色还是绿色?谁有优先权?
这个问题应该强调你的问题没有简单/明显的答案!
回到'a / b' - 你所做的(这在面向对象的语言中很难避免)是你定义了操作数上可用的操作。它是具有“除法”方法的“a”。并不是说“除法”这个概念是一个对象,它有一个带有 2 个参数的方法。换句话说,你有这个:
a.divide(b);
而不是这个:
fractionMathSystem.divide(a, b);
使a.divide(b)
工作本质上关联“左”的基本设计(例如,“a”决定了除法的实现,b 只与右关联),并且不关联“右”,因为这就是 java 的定义方式。
问题是,您不希望在这里留下关联性:如果 'b' 未定义而 'a' 未定义,则您希望 'b' “覆盖”并控制操作。
但是你不能这样做,并且由于前面提到的红色/绿色未定义问题,没有简单的语言建议没有任何警告可以做到这一点。
根本问题是您已经定义了一个运算(分数的除法),它应该根据运算本身而不是根据任一操作数来定义。因此,fractionNumberSystem.divide(a, b)
更有意义;您可以在此处编写“如果任一操作数未定义,则结果未定义”的代码,甚至可以引入一个系统,询问每个操作数是否要负责操作,并为每个操作数提供优先级层次结构,其中数字系统将选择报告它具有最高优先级的操作数,并要求它执行操作。
幸运的是,您可以a.divide(b)
归结为:
public class Fraction {
public void divide(Fraction rhs) {
return numberSystem.divide(this, rhs);
}
}
现在 numberSystem.divide 可以做任何你想做的事情。“除法操作首先关联到未定义,无论未定义在哪一边”的概念然后可以在 numberSystem 的除法方法中编程。或者,你可以让它更“可插拔”,想办法解决红/绿问题(当双方都想控制操作时,你如何决定哪个“赢”?是 red.add(green ) 红色还是绿色?),随心所欲。也许通过检查所涉及的 Fraction 是否是“OperationControllingFraction”(您要创建的子类型)的实例,并且使 OperationControllingFraction 类型具有radd
方法(右关联添加)以及int priority()
方法。
注意:提示:add
是一个坏名字。plus
效果更好。a.add(b)
建议这修改a
. a.plus(b)
建议 a 保持不变,并返回一个新值。是的,BigInteger
而且朋友的名字很糟糕。它发生了;这些 API 已经很老了,考虑到设计错误已经足够严重,我认为延续明显的设计错误以保持一致并不是一个好主意。
推荐阅读
- nginx - 如何使用 nginx 发布两个 odoo 实例,使用不同的路径而不是不同的服务器名称或端口?
- c# - 从字符串中读取命令并在 C# 中执行它们各自函数的最佳方法?
- c++ - Arduino MKR Wifi 1010 与 Arduino Mega 的串行通信
- django - 选择每小时的最新记录
- go - Golang:如何读取嵌套 JSON 对象中的数据?
- javascript - setColor 不是函数
- node.js - Node.js Express Web App 在 localhost 中有效,但在发布到 Azure 时无效
- algorithm - 最小变化,因此每 k 个连续元素的 XOR 为 0
- visual-studio-code - 如何在 F# 源文件 (*.fs) 中使用 AKKA?
- java - swagger codegen maven覆盖生成的具有相同名称但大小写不同的文件