您的位置:首页 > 编程语言

函数式编程思想:耦合和组合,第1部分

2016-05-09 15:38 387 查看

面向对象编程通过封装变动部分把代码变成易懂的,函数式编程则是通过最小化变动部分来把代码变成易懂的。

——Michael Feathers,Working with Legacy Code一书的作者

清单1.命令式的数字分类器

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import static java.lang.Math.sqrt;

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;
}

}


清单2. 素数测试,以命令方式来编写

import java.util.HashSet;
import java.util.Set;

import static java.lang.Math.sqrt;

public class PrimeAlpha {
private int number;

public PrimeAlpha(int number) {
this.number = number;
}

public boolean isPrime() {
Set primeSet = new HashSet() {{
add(1); add(number);}};
return number > 1 &&
factors().equals(primeSet);
}

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;
}
}


在 清单 2 中有几点需要注意。第一个是在 isPrime() 方法中加入了有点奇怪的初始化代码。这是一个关于实例初始值设定项 的示例。了解更多关于实例初始化的信息(函数式编程所附带的一种 Java 技术),请参阅 “演化架构和紧急设计: 利用可重用代码,第 2 部分”。

清单 2 中另要注意的是 isFactor() 和 factors() 方法。注意,它们与 ClassifierAlpha 类(在 清单 1)中的对应方法相同。这是单独实施两个解决方案并意识到您实际上有两个相同的功能所产生的自然结果。

清单 3. 常用的重构分解代码

import java.util.Set;
import static java.lang.Math.sqrt;
import java.util.HashSet;

public class FactorsBeta {
protected int number;

public FactorsBeta(int number) {
this.number = number;
}

public boolean isFactor(int potential_factor) {
return number % potential_factor == 0;
}

public Set<Integer> getFactors() {
HashSet<Integer> factors = new HashSet<Integer>();
for (int i = 1; i <= sqrt(number); i++)
if (isFactor(i)) {
factors.add(i);
factors.add(number / i);
}
return factors;
}
}


清单4 重构和简化数字分类器

import java.util.Iterator;
import java.util.Set;

public class ClassifierBeta extends FactorsBeta {

public ClassifierBeta(int number) {
super(number);
}

public int sum() {
Iterator it = getFactors().iterator();
int sum = 0;
while (it.hasNext())
sum += (Integer) it.next();
return sum;
}

public boolean isPerfect() {
return sum() - number == number;
}

public boolean isAbundant() {
return sum() - number > number;
}

public boolean isDeficient() {
return sum() - number < number;
}

}


清单 5. 重构和简化的质数检验器

import java.util.HashSet;
import java.util.Set;

public class PrimeBeta extends FactorsBeta {
public PrimeBeta(int number) {
super(number);
}

public boolean isPrime() {
Set<Integer> primeSet = new HashSet<Integer>() {{
add(1); add(number);}};
return getFactors().equals(primeSet);
}
}


通过组合实现代码重用

在本系列的 第 2 部分中,我介绍了 Java 函数版的数字分类器,如清单 6 所示:

清单 6. 更加函数化版本的数字分类器

public class FClassifier {

static public boolean isFactor(int number, int potential_factor) {
return number % potential_factor == 0;
}

static public Set<Integer> factors(int number) {
HashSet<Integer> factors = new HashSet<Integer>();
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<Integer> 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 static boolean isDeficient(int number) {
return sumOfFactors(number) - number < number;
}
}


我还有一个函数版的质数检验器(采用纯函数,无共享状态),其中的 isPrime() 方法如清单 7 所示。代码的其余部分与 清单 6 中同名方法相同。

清单 7. 函数版的质数检验器

public static boolean isPrime(int number) {
Set<Integer> factors = factors(number);
return number > 1 &&
factors.size() == 2 &&
factors.contains(1) &&
factors.contains(number);
}


正如我在命令式版本中所做的一样,我将重复的代码提取到其 Factors 类中,并且为便于阅读,将 factors 方法的名称改为 of ,如清单 8 所示:

清单 8. 函数化重构的 Factors 类

import java.util.HashSet;
import java.util.Set;
import static java.lang.Math.sqrt;

public class Factors {
static public boolean isFactor(int number, int potential_factor) {
return number % potential_factor == 0;
}

static public Set<Integer> of(int number) {
HashSet<Integer> factors = new HashSet<Integer>();
for (int i = 1; i <= sqrt(number); i++)
if (isFactor(number, i)) {
factors.add(i);
factors.add(number / i);
}
return factors;
}
}


因为函数版中的所有状态都被作为参数传递,提取时并没有出现共享状态。一旦提取了该类之后,我就能通过同时重构函数分类器和质数检验器来使用它。 清单 9 中显示了重构的数字分类器。

因为函数版中的所有状态都被作为参数传递,提取时并没有出现共享状态。一旦提取了该类之后,我就能通过同时重构函数分类器和质数检验器来使用它。 清单 9 中显示了重构的数字分类器。

清单 9. 重构的数字分类器

public class FClassifier {

public static int sumOfFactors(int number) {
Iterator<Integer> 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 显示了重构的质数检验器:

import java.util.Set;

public class FPrime {

public static boolean isPrime(int number) {
Set<Integer> factors = Factors.of(number);
return number > 1 &&
factors.size() == 2 &&
factors.contains(1) &&
factors.contains(number);
}
}


注意:我并没有使用任何特殊的库或语言来使第二个版本更加函数化。相反,我是通过使用组合 而非耦合来实现代码重用。清单 9 和 清单 10 都使用了 Factors 类,但是对于它的使用是完全包含在单独的方法中的。

耦合与组合的区别非常细微但是又很重要。举一个简单的例子,您可以看到呈现代码结构的框架。然而,当您重构大型的代码库后,到处都会出现耦合的地方,因为那是面向对象语言中的其中 一种重用机制。对于丰富的耦合结构的理解困难阻碍了面向对象语言中的重用,限制了定义明确的技术域(如对象关系映射和小部件库)的有效重用。在编写不太明显的结构化 Java 代码时(比如您在业务应用程序中所编写的代码),我们无法做到同一级的重用。

结束语

像更加函数式的程序员一样思考指的是换个方式来考虑编码的各个方面。代码重用是一个显著的开发目标,而命令式抽象倾向于用不同于函数式程序员解决问题的方法来解决问题。本文对两种代码重用进行了比较:通过继承实现的耦合和通过参数实现的组合。下一篇文件将继续讨论这一重要的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息