public class ClassifierAlpha {
private int number;
public ClassifierAlpha(int number) {
this.number = number;
}
public boolean isFactor(int potential_factor) {
return number % potential_factor == 0;
}
public Set factors() {
HashSet factors = new HashSet();
for (int i = 1; i <= sqrt(number); i++)
if (isFactor(i)) {
factors.add(i);
factors.add(number / i);
}
return factors;
}
static public int sum(Set factors) {
Iterator it = factors.iterator();
int sum = 0;
while (it.hasNext())
sum += (Integer) it.next();
return sum;
}
public boolean isPerfect() {
return sum(factors()) - number == number;
}
public boolean isAbundant() {
return sum(factors()) - number > number;
}
public boolean isDeficient() {
return sum(factors()) - number < number;
}
public class PrimeBeta extends FactorsBeta {
public PrimeBeta(int number) {
super(number);
}
public boolean isPrime() {
Set primeSet = new HashSet() {{
add(1); add(number);}};
return getFactors().equals(primeSet);
}
}
无论在重构时为number成员选择的访问选项是哪一种,你在考虑这一问题时都必须要处理类之间的网络关系。通常这是一件好事,因为其允许你独立出问题的某些部分,但在修改父类时也会带来不利的后果。
这是一个通过耦合(coupling)来重用代码的例子:通过number域这一共享状态和超类的getFactors()方法来把两个元素(在本例中是类)捆绑在一起。换句话说,这种做法起作用是因为利用了内置在语言中的耦合规则。面向对象定义了耦合的交互方式(比如说,你通过继承访问成员变量的方式),因此你拥有了关于事情如何交互的一些预定义好的风格——这没有什么问题,因为你可以以一种一致的方式来推理行为。不要误解我——我并非是在暗示使用继承是一个糟糕的主意,相反,我的意思是,它在面向对象的语言中被过度使用,结果取代了另一种有着更好特性的抽象。
经由组合的代码重用
在这一系列的第二部分内容中,我给出了一个用Java编写的数字分类器的函数式版本,如清单6所示:
清单6. 数字分类器的一个更加函数化的版本
public class FClassifier {
static public boolean isFactor(int number, int potential_factor) {
return number % potential_factor == 0;
}
static public Set factors(int number) {
HashSet factors = new HashSet();
for (int i = 1; i <= sqrt(number); i++)
if (isFactor(number, i)) {
factors.add(i);
factors.add(number / i);
}
return factors;
}
public static int sumOfFactors(int number) {
Iterator it = factors(number).iterator();
int sum = 0;
while (it.hasNext())
sum += it.next();
return sum;
}
public static boolean isPerfect(int number) {
return sumOfFactors(number) - number == number;
}
public static boolean isAbundant(int number) {
return sumOfFactors(number) - number > number;
}
public class Factors {
static public boolean isFactor(int number, int potential_factor) {
return number % potential_factor == 0;
}
static public Set of(int number) {
HashSet factors = new HashSet();
for (int i = 1; i <= sqrt(number); i++)
if (isFactor(number, i)) {
factors.add(i);
factors.add(number / i);
}
return factors;
}
}
因为函数式版本中所有状态都是作为参数传递的,因此提取出来的这部分内容没有共享状态。一旦提取了该类之后,我就可以重构函数式的分类器和素数测试器来使用它了。清单9给出了重构后的分类器:
清单9. 重构后的数字分类器
public class FClassifier {
public static int sumOfFactors(int number) {
Iterator it = Factors.of(number).iterator();
int sum = 0;
while (it.hasNext())
sum += it.next();
return sum;
}
public static boolean isPerfect(int number) {
return sumOfFactors(number) - number == number;
}
public static boolean isAbundant(int number) {
return sumOfFactors(number) - number > number;
}
public static boolean isDeficient(int number) {
return sumOfFactors(number) - number < number;
}
}
清单10给出了重构后的素数测试器:
清单10. 重构后的素数测试器
import java.util.Set;
public class FPrime {
public static boolean isPrime(int number) {
Set factors = Factors.of(number);
return number > 1 &&
factors.size() == 2 &&
factors.contains(1) &&
factors.contains(number);
}
}
可以注意到,我并未使用任何特殊的库或是语言来把第二个版本变得更加的函数化,相反,我通过使用组合而不是耦合式的代码重用做到了这一点。清单9和清单10都用到了Factors类,但它的使用完全是包含在了单独方法的内部之中。
耦合和组合之间的区别很细微但很重要,在一个像这样的简单例子中,你可以看到显露出来的代码结构骨架。但是,当你最终重构的是一个大型的代码库时,耦合就显得无处不在了,因为这是面向对象语言中的重用机制之一。繁复的耦合结构的难以理解性损害到了面向对象语言的重用性,把有效的重用局限在了诸如对象-关系映射和构件库一类已明确定义的技术领域上,当我们在写少量的明显结构化的Java代码时(比如说你在业务应用中编写的代码),这种层面的重用我们就用不上了。
你可以通过这样的做法来改进命令式的版本,即在重构期间会告之哪些内容由IDE提供,先客气地拒绝,然后使用组合来替代。
结束语
作为一个更函数化的编程者来进行思考,这意味着以不同的方式来思考编码的各个方面。代码重用显然是开发的一个目标,命令式抽象倾向于以不同于函数式编程者的方式来解决该问题。这部分内容对比了代码重用的两种方式:经由继承的耦合方式和经由参数的组合方式。下一部分内容会继续探讨这一重要的分歧。