《Java 8 函数式编程》笔记6

测试、调试和重构

孤独的覆盖

ThreadLocal 能创建一个工厂,为每个线程最多只产生一个值。这是确保非线程安全的类在并发环境下安全使用的一种简单方式。

假设要在数据库查询一个艺术家,但希望每个线程值做一次这种查询:

1
2
3
4
5
6
ThreadLocal<Album> thisAlbum = new ThreadLocal<Album>() {
@Override
protected Album initialValue() {
return database.findCurrentAlbum();
}
};

为工厂方法 withInitial 传入一个 Supplier 对象实例来创建对象:

1
2
3
ThreadLocal<Album> thisAlbum = ThreadLocal.withInitial(
() -> database.findCurrentAlbum()
);

同样的东西写两遍

DRY:Don’t Repeat Yourself
WET:Write Everything Twice

不是所有的 WET 都适合 lambdas 化。有时重复是唯一可以避免系统过紧耦合的方式。

什么时候该将 WET 的代码 lambda 化?
如果有一个整体上大概相似的模式,只是行为上有所不同,就可以试着加入一个 lambda 表达式。

举个栗子:

用户想要了解购买的专辑的一些信息,比如音乐家的人数、曲目和专辑时长等。

使用命令式 Java 编写的 Order 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public long countMusicians() {
long count = 0;
for (Album album : albums) {
count += album.getMusicianList().size();
}
return count;
}

public long countTracks() {
long count = 0;
for (Album album : albums) {
count += album.getTrackList().size();
}
return count;
}

public long countRunningTime() {
long count = 0;
for (Album album : albums) {
for (Track track : album.getTrackList()) {
count += track.getLength();
}
}
return count;
}

每个方法里,都有样板代码将将每个专辑里的属性和总数相加。
没有重用共有的概念,写出了更多需要测试和维护的代码。

新增 OrderStream 类,使用 Stream 来抽象 Order 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public long countMusicians() {
return albums.stream()
.mapToLong(album -> album.getMusicians().count())
.sum();
}

public long countTracks() {
return albums.stream()
.mapToLong(album -> album.getTracks().count())
.sum();
}

public long countRunningTime() {
return albums.stream()
.mapToLong(album -> album.getTracks()
.mapToLong(Track::getLength)
.sum())
.sum();
}

然而这段代码仍然有重用可读性的问题,因为有一些抽象和共性只能使用领域内的知识来表达。
流不会提供一个方法统计每张专辑上的信息——这是程序猿自己要编写的领域知识。

新增 OrderStreamDSL 类,用领域方法重构 OrderStream 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private long countFeature(ToLongFunction<Album> function) {
return albums.stream()
.mapToLong(function)
.sum();
}

public long countMusicians() {
return countFeature(album -> album.getMusicians().count());
}

public long countTracks() {
return countFeature(album -> album.getTracks().count());
}

public long countRunningTime() {
return countFeature(album -> album.getTracks()
.mapToLong(Track::getLength)
.sum());
}
Author

Zoctan

Posted on

2018-03-05

Updated on

2023-03-14

Licensed under