利用枚举实现类似工厂模式的自动装配

利用枚举实现接口的方式可以达到类似工厂模式一样的自动装配体验

Posted by youthred on May 20, 2022

一般使用枚举的普通用法就是switch case,但其类型一旦多起来也不太方便不便维护。

使用实现接口的方式可以达到类似工厂模式一样的自动装配体验。

假定需求:要求从配置文件读取时间配置如“1年”、“5天”或“2周”等数据,取出以当前时间为基准的“1年前”或“1月前”的某某数据。

定义一个日期单位枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@AllArgsConstructor
public enum DatePicker {

    H("小时"),
    D("天"),
    W("周"),
    M("月"),
    Y("年"),
    ;

    final private String cn;
}

此时从配置文件读取对应配置例如1y,意为“一年”,计算“一年前”的时间节点。

  • switch case方式计算
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
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import io.github.youthred.es.cleaner.common.DatePicker;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
class EnumTest {

    @Test
    void before() {
        String before = "1y";
        String dateUnitName = StrUtil.sub(StrUtil.reverse(before), 0, 1).toUpperCase();
        int num = Integer.parseInt(StrUtil.sub(before, 0, -1));
        try {
            DatePicker datePicker = DatePicker.valueOf(dateUnitName);
            LocalDateTime beforeTimePoint;
            switch (datePicker) {
                case H:
                    beforeTimePoint = LocalDateTime.now().minusHours(num);
                    break;
                case D:
                    beforeTimePoint = LocalDateTime.now().minusDays(num);
                    break;
                case W:
                    beforeTimePoint = LocalDateTime.now().minusWeeks(num);
                    break;
                case M:
                    beforeTimePoint = LocalDateTime.now().minusMonths(num);
                    break;
                case Y:
                    beforeTimePoint = LocalDateTime.now().minusYears(num);
                    break;
                default:
                    throw new IllegalArgumentException("不支持的枚举类型:" + datePicker);
            }
            log.info("此时 {},读取 {} 之前也就是 {} 之前(含)的数据",
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)),
                    num + datePicker.getCn(),
                    beforeTimePoint.format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))
            );
        } catch (IllegalArgumentException e) {
            log.error("单位 {} 转化DatePicker枚举失败:{}", dateUnitName, ExceptionUtil.getSimpleMessage(e));
        }
    }
}

执行结果

1
INFO-2022-05-20 15:32:58-[main]-EnumTest:此时 2022-05-20 15:32:58,读取 1年 之前也就是 2021-05-20 15:32:58 之前(含)的数据

若是枚举类型新增,那switch case代码块里也需要跟着新增对应代码,这样比较繁琐且代码耦合。

改为实现接口的方式。

  • 定义接口
1
2
3
4
5
6
import java.time.LocalDateTime;

public interface Before {

    LocalDateTime minus(long num);
}
  • 枚举实现该接口
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
package io.github.youthred.es.cleaner.common;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@AllArgsConstructor
public enum DatePicker implements Before {

    H("小时") {
        @Override
        public LocalDateTime minus(long num) {
            return LocalDateTime.now().minusHours(num);
        }
    },
    D("天") {
        @Override
        public LocalDateTime minus(long num) {
            return LocalDateTime.now().minusDays(num);
        }
    },
    W("周") {
        @Override
        public LocalDateTime minus(long num) {
            return LocalDateTime.now().minusWeeks(num);
        }
    },
    M("月") {
        @Override
        public LocalDateTime minus(long num) {
            return LocalDateTime.now().minusMonths(num);
        }
    },
    Y("年") {
        @Override
        public LocalDateTime minus(long num) {
            return LocalDateTime.now().minusYears(num);
        }
    },
    ;

    final private String cn;

    public static List<String> names() {
        return Arrays.stream(values())
                .map(DatePicker::name)
                .collect(Collectors.toList());
    }
}

每个类型的实现代码是省不了的,但这样可以很好地解耦,且易于维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void beforeByInterface() {
    String before = "1y";
    String dateUnitName = StrUtil.sub(StrUtil.reverse(before), 0, 1).toUpperCase();
    int num = Integer.parseInt(StrUtil.sub(before, 0, -1));
    try {
        DatePicker datePicker = DatePicker.valueOf(dateUnitName);

        // 消灭 switch case
        LocalDateTime beforeTimePoint = datePicker.minus(num);
        
        log.info("此时 {},读取 {} 之前也就是 {} 之前(含)的数据",
                LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)),
                num + datePicker.getCn(),
                beforeTimePoint.format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN))
        );
    } catch (IllegalArgumentException e) {
        log.error("单位 {} 转化DatePicker枚举失败:{}", dateUnitName, ExceptionUtil.getSimpleMessage(e));
    }
}