Fork me on GitHub
0%

Java 中通过枚举实现单例和策略模式

在上篇文章中主要介绍了 Java 中对于枚举的合理使用能让我们的代码看起来更优雅,但其实枚举还有一个特异功能,可以实现设计模式中的单例模式。

先来看我们最熟悉的单例模式,我们之前看到的最多的单例模式实现是像下面这种,线程安全的双重检查锁懒汉单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ThreadSafeSingleton {

private volatile static ThreadSafeSingleton singleton;

private ThreadSafeSingleton() {
}

public ThreadSafeSingleton getInstance() {
if (singleton != null) {
return singleton;
}
synchronized (ThreadSafeSingleton.class) {
if (singleton == null) {
singleton = new ThreadSafeSingleton();
}
}
return singleton;
}

}

上面这种实现方式是懒汉式单例,同时通过双重检查锁规避了线程安全问题和性能问题,同时利用 volatile 关键字杜绝 JVM 的指令重排,避免了多线程情况下可能出现 NPE 的问题,上面的实现方式既做到了线程安全,同时又不失性能的单例实现。

那么这里我么说到通过枚举来实现单例又是如何实现的呢,其实很简单,比如下面的代码就是使用枚举来实现的一个单例。

1
2
3
4
5
public enum DeployEnvSingleton {

INSTANCE;

}

上面代码实现了一个很简单的单例模式,虽然看起来非常简单,但是做的事情一点也不比上面开头提出的单例模式少,甚至在某些极端情况下还有特殊的效果。先来看看这个简单的单例做到了哪些事情:

  • 只会创建一个实例,因为枚举类型可确保 JVM 中仅存在所定义的常量的一个实例
  • 线程安全,因为 Java 虚拟机在加载枚举类的时候,会使用 ClassLoader 的 loadClass 方法,而这个方法使用了同步代码块来保证线程安全
  • 反射安全,因为JVM 会阻止反射获取枚举类的私有构造方法,而其他的单例实现是可以通过反射来创建对象的
  • 序列化/反序列化安全,你用序列化工具对该实例进行序列化然后再反序列化会发现得到值是同一个
  • 优雅简单,从代码就可以看出来实现非常简单

从列举的这些可以看出使用枚举来实现单例是非常好的选择,当然唯一的一个缺点是不能实现懒加载,枚举中的实例不管我们有没有用到它,它都已经创建好了在那里。

说完单例,再看下策略模式又是怎样的。一般我们实现策略模式是通过实现接口的方式来实现的,每加一个新的策略就加一个子类实现相应的接口

strategy_uml

上面就是策略模式的 UML 图,Strategy 是接口,ConcreteStrategyA 和 ConcreteStrategyB 是具体的策略实现,增加的新的策略则再加一个子类实现 Strategy 接口,然后我们再看采用枚举的实现方式。

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 enum EnvEnum {

DEVELOPMENT{
@Override
public void deploy(Issue issue) {
System.out.println(issue.getId() + " issue will deploy to DEVELOPMENT");
}
},

TESTING{
@Override
public void deploy(Issue issue) {
System.out.println(issue.getId() + " issue will deploy to TESTING");
}
},

PRODUCTION{
@Override
public void deploy(Issue issue) {
System.out.println(issue.getId() + " issue will deploy to PRODUCTION");
}
};

public abstract void deploy(Issue issue);
}

上面的代码就是一个简易的策略模式,预先定义了三个环境,dev,test,prod,后期如果想要再加一个环境 staging 只需要在枚举类中再加一个 STAGING 实例即可。

整体看下来是不是觉得枚举这个类型还是可以做到很多特性的,不过我最喜欢枚举的一点还是它能够帮助我们写出易于阅读同时又非常优雅的代码。所以说好好利用枚举这个类型,也让你代码实现更优雅一些吧。

完整测试代码地址:Java basic sample

参考:https://www.baeldung.com/a-guide-to-java-enums

 wechat
扫描上面图中二维码关注微信公众号