1. 枚举类
1.1. 定义
简单创建一个枚举类
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
26
27
28
29
30
31
@Getter
enum WeekdayEnum {
Monday(1, "星期一"),
Tuesday(2, "星期二"),
Wednesday(3, "星期三"),
Thursday(4, "星期四"),
Friday(5, "星期五"),
Saturday(6, "星期六"),
Sunday(7, "星期天"),
;
private static final HashMap<Integer, WeekdayEnum> enumHashMap = new HashMap<>();
private final Integer code;
private final String desc;
static {
for (WeekdayEnum value : values()) {
enumHashMap.put(value.getCode(), value);
}
}
WeekdayEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public static WeekdayEnum getEnumByCode(Integer code){
return enumHashMap.get(code);
}
}
该枚举类穷举了一周内的所有天,并建立了一个基于 HashMap 的本地缓存,通过缓存向外界提供了 getEnumByCode(),即通过 code 获取对应的枚举。当类的对象是有限的,该类适合定义成枚举类。
枚举类的特性
反编译 WeekdayEnum.class 能得到下面的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.HashMap;
final class WeekdayEnum extends Enum{
public static WeekdayEnum[] values(){
return (WeekdayEnum[])$VALUES.clone();
}
public static final WeekdayEnum Monday;
public static final WeekdayEnum Tuesday;
public static final WeekdayEnum Wednesday;
private final Integer code;
private final String desc;
private static final WeekdayEnum $VALUES[];
static {
Monday = new WeekdayEnum("Monday", 0, Integer.valueOf(1), "\u661F\u671F\u4E00");
Tuesday = new WeekdayEnum("Tuesday", 1, Integer.valueOf(2), "\u661F\u671F\u4E8C");
$VALUES = (new WeekdayEnum[] {
Monday, Tuesday, Wednesday
});
}
}
通过这段反编译代码,我们发现编译器为我们做了这些事情:
- 枚举类是一个被 final 修饰的普通类,即它无法被继承。
- 构造函数新增两个参数,String 类型的 s,和 int 类型的 i,分别是枚举对象的变量名和排序值(从 0 开始)
- 新增一个 valueOf(),即通过变量名获取枚举对象
- 新定义了一个枚举数组 $VALUES 缓存所有的枚举,以及一个返回数组拷贝的 values()
以上便是编译器为定义的枚举类额外做的事,我们发现枚举类就是一个普通的类,并且如果只有一个枚举对象,那么它其实就是一个单例模式的实现。
1.3. 枚举类 vs 常量类
枚举类和常量类没有可比性,因为枚举是作为对象而存在,并且能使用常量类的地方一定可以用枚举类替换,但使用枚举类的地方不一定能用常量类替换,枚举类作为对象使用时能应用到的场景更多更广,总而言之枚举类可玩性更强。
经典策略模式
策略模式也属于一种行为型模式,在实际的开发中这个模式也用的非常多,最常见的应用场景是利用它来避免冗长的 if-else 或 switch 分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。
定义
策略模式,英文全称是Strategy Design Pattern:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略 模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。其实就是一种解耦方式,们知道,工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者,终究是应用的场景不同。
实现
假设有这样一个需求,希望写一个小程序,实现对一个文件进行排序的功能。文件中只包含 整型数,并且,相邻的数字通过逗号来区隔。
很简单:先将文件中的内容读出来,以逗号分隔将其读取成一个个的数字,再以任意一种排序算法对其进行排序,并将排序好的数据重新写入文件当中。
我猜你肯定是这么想的,但是这样存在问题,比如文件 size 非常大,像 10GB,50GB,甚至 100GB,那么内存根本不够,跟别说还在内存中排序了想都别想。这时候我们应该根据文件的 size 合理分析:
- 当文件 size < 6GB(现在的手机、电脑,只要稍微好点的内存至少有 6GB 吧),那么直接读取到内存并使用快排。
- 10GB <= size < 100GB 这时候没办法加入到内存当中,那么就使用外部排序算法。
- 100GB <= size < 1T,为了利用 CPU 多核的优势可以在外部排序的基础之上进行优化,加入多线程并发排序的功能,这就有点类似“单机版”的MapReduce。
- size > 1T,这时候即便是单机多线程排序,也算很慢了。这样可以使用真正的 MapReduce 框架,利用多机的处理能力,提高排序的效率。
先用最简单的方式实现:
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
26
27
28
29
30
31
32
33
34
35
public class Sorter {
private static final long GB = 1000 * 1000 * 1000;
public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
if (fileSize < 6 * GB) { // [0, 6GB)
quickSort(filePath);
} else if (fileSize < 10 * GB) { // [6GB, 10GB)
externalSort(filePath);
} else if (fileSize < 100 * GB) { // [10GB, 100GB)
concurrentExternalSort(filePath);
} else { // [100GB, ~)
mapreduceSort(filePath);
}
}
private void quickSort(String filePath) {
// 快速排序
}
private void externalSort(String filePath) {
// 外部排序
}
private void concurrentExternalSort(String filePath) {
// 多线程外部排序
}
private void mapreduceSort(String filePath) {
// 利用MapReduce多机排序
}
}
public class SortingTool {
public static void main(String[] args) {
Sorter sorter = new Sorter();
sorter.sortFile(args[0]);
}
}
这段代码确实很简单,但是类看起来会很臃肿,因为上面的几个算法的内容会很多,同时如果后续增加算法,就需要增加 if/else,同时几个算法函数作为私有函数放在 Sorter 类中,这就没法给其它类复用了。综上可优化重构将这几个算法函数独立出来成为单个类,正好可以用到策略模式:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 定义和创建策略
* @see ISortAlg、QuickSort、ExternalSort、ConcurrentExternalSort 和 MapReduceSort
*/
public interface ISortAlg {
void sort(String filePath);
}
public class QuickSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class ExternalSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class ConcurrentExternalSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
public class MapReduceSort implements ISortAlg {
@Override
public void sort(String filePath) {
//...
}
}
/**
* 使用策略
*/
public class Sorter {
private static final long GB = 1000 * 1000 * 1000;
public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg;
if (fileSize < 6 * GB) { // [0, 6GB)
sortAlg = new QuickSort();
} else if (fileSize < 10 * GB) { // [6GB, 10GB)
sortAlg = new ExternalSort();
} else if (fileSize < 100 * GB) { // [10GB, 100GB)
sortAlg = new ConcurrentExternalSort();
} else { // [100GB, ~)
sortAlg = new MapReduceSort();
}
sortAlg.sort(filePath);
}
}
经过重构,我们将排序算法设计成独立的类,跟具体的业务逻辑(代码中的 if-else那部分逻辑)解耦,也让排序算法能够复用
实际上上面的代码还可以优化,因为算法是无状态的,跟具体的业务无关,所以没必要每次使用的时候都重新创建一个对象。所以可有使用工厂模式对其对象的创建进行封装:
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
26
27
28
29
30
31
32
33
34
35
public class SortAlgFactory {
private static final Map<String, ISortAlg> algs = new HashMap<>();
static {
algs.put("QuickSort", new QuickSort());
algs.put("ExternalSort", new ExternalSort());
algs.put("ConcurrentExternalSort", new ConcurrentExternalSort());
algs.put("MapReduceSort", new MapReduceSort());
}
public static ISortAlg getSortAlg(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return algs.get(type);
}
}
public class Sorter {
private static final long GB = 1000 * 1000 * 1000;
public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg;
if (fileSize < 6 * GB) { // [0, 6GB)
sortAlg = SortAlgFactory.getSortAlg("QuickSort");
} else if (fileSize < 10 * GB) { // [6GB, 10GB)
sortAlg = SortAlgFactory.getSortAlg("ExternalSort");
} else if (fileSize < 100 * GB) { // [10GB, 100GB)
sortAlg = SortAlgFactory.getSortAlg("ConcurrentExternalSort");
} else { // [100GB, ~)
sortAlg = SortAlgFactory.getSortAlg("MapReduceSort");
}
sortAlg.sort(filePath);
}
}
通过两次重构,现在的代码已经符合策略模式的代码结构了,不过,Sorter类中的 sortFile()函数还是有一堆if-else逻辑。这里的if-else逻辑分支不多、也不复杂,这样写完全 没问题。但是如果一定要消除 if-else,那么可以依据策略工厂的这种”查表法”,再弄一个工厂:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Sorter {
private static final long GB = 1000 * 1000 * 1000;
private static final List<AlgRange> algs = new ArrayList<>();
static {
algs.add(new AlgRange(0, 6*GB, SortAlgFactory.getSortAlg("QuickSort")));
algs.add(new AlgRange(6*GB, 10*GB, SortAlgFactory.getSortAlg("ExternalSort")));
algs.add(new AlgRange(10*GB, 100*GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort")))
algs.add(new AlgRange(100*GB, Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort")))
}
public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg = null;
for (AlgRange algRange : algs) {
if (algRange.inRange(fileSize)) {
sortAlg = algRange.getAlg();
break;
}
}
sortAlg.sort(filePath);
}
private static class AlgRange {
private long start;
private long end;
private ISortAlg alg;
public AlgRange(long start, long end, ISortAlg alg) {
this.start = start;
this.end = end;
this.alg = alg;
}
public ISortAlg getAlg() {
return alg;
}
public boolean inRange(long size) {
return size >= start && size < end;
}
}
}
相应的这里用的是 List 结构遍历查询,而非 Map 的匹配查询。现在当新增策略时,我们只需要新增一个策略类,然后两个工厂类中的静态代码块加上对应的候选项就可了。
当然也许有人会说这样封装的还是不彻底呀,还是需要修改代码,那么我们可以参照 Spring 中的设计,通过为策略类打上注解/配置文件的方式,并通过反射创建策略类对象从而避免对工厂类的修改。