首页 > 技术文章 > GOF设计模式之单例模式

throwable 2018-07-29 10:18 原文

定义

单例模式(Singleton Pattern)的定义如下:Ensure a class only has one instance, and provide a global point of access to it(确保某一个类只有一个实例,并且提供一个全局访问点来访问此实例)。在JVM应用中,单例模式表现为一个类在JVM中只有一个实例。一个相对合理的类图如下:

sg-1

使用场景

  • 1、系统中需要一个共享的访问点或者共享数据,例如Web请求计数器。
  • 2、创建一个对象需要消耗的资源过多,例如IO、数据库资管等。
  • 3、需要定义大量的静态常量或者静态方法(例如工具类),可以考虑采用单例模式。

在JDK中典型的真实例子如下:

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

适用性

单例模式的优势

  • 采用单例模式的类能确保在一个应用中只有一个实例,减少了内存消耗以及创建或者销毁类实例时候的性能损耗。
  • 可以避免对资源的多重占用。
  • 可以设置应用的全局访问点,优化和共享资源访问。

单例模式的劣势

  • 单例模式一般没有接口或者基类,扩展困难,扩展必须修改类代码。
  • 紧密耦合的代码,对测试不利,简单来说就是不能Mock掉。
  • 单例模式违反单一责任原则,因为它既要保持"单例"又要顾及业务逻辑。

实现方式

懒汉方式

懒汉方式的关键字在于"懒",也就是懒加载(Lazy Load),一个很常见的使用方式就是双重检查锁定(Double-Check Locking):

public class Singleton {

	private static volatile Singleton INSTANCE = null;
	
	private Singleton(){

	}

	public static Singleton getInstance(){
		if (null == INSTANCE){
			synchronized (Singleton.class){
				if (null == INSTANCE){
					INSTANCE = new Singleton();
				}
			}
		}
		return INSTANCE;
	}
}

饿汉方式

饿汉方式的实现相对简单:

public class Singleton {

	private static volatile Singleton INSTANCE = new Singleton();

	private Singleton(){

	}

	public static Singleton getInstance(){
		return INSTANCE;
	}
}

静态内部类方式

使用静态内部类方式的好处是既可以实现延迟加载,又可以保证线程安全,它的实现如下:

public class Singleton {

	private Singleton(){

	}
	
	private static class InterClassHolder{
		private final static Singleton INSTANCE = new Singleton();
	}

	public static Singleton getInstance(){
		return InterClassHolder.INSTANCE;
	}
}

最佳实践-单元素枚举方式

《Effective Java》第2版中指出:单元素枚举类型是实现单例的最佳方式。这是因为,前面说到的三种实现方式都可以通过反射改变类的行为,但是枚举类型可以避免这个问题。建议在所有需要使用到单例模式的情况下直接使用单元素枚举方式实现单例:

public enum Singleton {

	INSTANCE;

	public void sayHello() {

	}
}

使用方式:Singleton.INSTANCE.sayHallo()

故事

Doge是公司里一个核心项目的开发组长,手下有十多个组员分别负责开发项目的不同模块。

sg-2

sg-3

Doge展示了一个日期工具类和它的使用情况:

public class DateUtils {

	public static String format(LocalDateTime target,String pattern){
		return DateTimeFormatter.ofPattern(pattern).format(target);
	}

	public LocalDateTime parse(String target,String pattern){
		return LocalDateTime.parse(target, DateTimeFormatter.ofPattern(pattern));
	}
}

//调用情况
DateUtils.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
LocalDateTime target = new DateUtils().parse("2018-7-29 12:12:30","yyyy-MM-dd HH:mm:ss");

sg-3

小黑贴了一下重写的工具类:

public enum DateUtils {

	SINGLETON;

	private static final Map<String, DateTimeFormatter> FORMATTER_CACHE = new HashMap<>();

	public String format(LocalDateTime target, String pattern) {
		return getOrCreateFormatter(pattern).format(target);
	}

	public LocalDateTime parse(String target, String pattern) {
		return LocalDateTime.parse(target, getOrCreateFormatter(pattern));
	}


	private DateTimeFormatter getOrCreateFormatter(String pattern) {
		DateTimeFormatter formatter;
		if (FORMATTER_CACHE.containsKey(pattern)) {
			formatter = FORMATTER_CACHE.get(pattern);
		} else {
			formatter = DateTimeFormatter.ofPattern(pattern);
			FORMATTER_CACHE.put(pattern, formatter);
		}
		return formatter;
	}
}

//调用
DateUtils.SINGLETON.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
LocalDateTime target = DateUtils.SINGLETON.parse("2018-7-29 12:12:30", "yyyy-MM-dd HH:mm:ss");

sg-3

Doge拷贝了小黑的工具类代码,并且仿照这个类的逻辑完成了其他工具类的代码重构。

(本文完)

技术公众号(《Throwable文摘》),不定期推送笔者原创技术文章(绝不抄袭或者转载):

娱乐公众号(《天天沙雕》),甄选奇趣沙雕图文和视频不定期推送,缓解生活工作压力:

推荐阅读