最近看了看程杰
前辈的《大话设计模式》
,主要是上次面试被问设计模式,答的很菜。。。经过一段时间的沉淀,现在来谈谈我对其单例模式
的理解。
一、单例模式
1、定义
单例模式
(Singleton),保证一个类仅有一个实例
,并且提供全局访问该实例
的接口。简单的说,既要保证该类只有一个实例
,也要保证全局访问的就是这个唯一的实例
。
2、使用场景
那么肯定有小伙伴会问,单例模式
有啥使用场景吗?限制一个类只有一个实例
,这种场景比较少吧。。。
确实,限制一个类只有一个实例
的场景比较少,但是并不代表单例模式
毫无用武之地。举个栗子,运用程序是如何维持某个窗口只一个的?比如系统桌面右键菜单。
如果不控制窗口对应的对象同一时刻只有一个,那么可能你每点一次,都会弹出一个同样的窗口。这显然不符合实际逻辑,也会造成资源的浪费。
其实还有另外一种普遍的场景,类中的静态属性
。我想写过面向对象编程序思想的程序小伙伴都在类中定义过static
属性,我们把某个变量加上static
关键字修饰,目的是把这个变量声明为类
本身所有,而不是类的实例
所有。最主要的作用不就是类的所有实例
都来访问这同一个static
属性,这正好与我们单例模式的思想吻合。
二、举例(静态属性)
上面提到了 类中的静态属性
是单例模式思想的一种特殊运用,下面我们通过代码来模拟一下。
public class SingleTon {
// 对object属性添加static修饰(这个变量属于SingleTon类,不属于实例)
public static MyClass object = new MyClass();
public static void main(String[] args) {
// 新建两个SingleTon对象
SingleTon singleTonOne = new SingleTon();
SingleTon singleTonTwo = new SingleTon();
// 由于Java提供通过类实例访问类静态属性的途径,所以这里我们测试一下
// 两个实例访问的是否为同一个实例object(这里只是演示,实际编写代码千万别通过对象访问类静态属性!!!)
System.out.println(singleTonOne.object);
System.out.println(singleTonTwo.object);
}
}
class MyClass{
public MyClass(){
}
}
由于在Java
语言中,每个类只能加载一次,因此SingleTon
类中的object
属性只会被初始化一次,也就是只有一个。通过观察程序的输出,确实发现两个实例共用SingleTon
类中的object
静态属性,做到了只有一个实例。但这个程序不能称为严格的单例
,因为在任何地方都可以通过调用MyClass
类的构造器来手动创建MyClass
的实例,因此这提示我们需要修改单例对应的类的构造器访问权限为私有(private关键字修饰)。在阅读下文前,可以思考一下如何改造这个程序,使MyClass
变为严格的单例。
三、两种实现方式(Java)
单例模式
的实现,常见的方法有两种,分别是懒汉式
、饿汉式
,两者都将类的构造器私有化,但是各有优缺点。
1、饿汉式
将构造器私有化
,并且用static属性
初始化一个实例。这样每次就只能访问同一个实例,并且限制了类的new操作(构造器被私有化了)。
public class SingleTonTest {
public static void main(String[] args) {
// 通过SingleTon.getInstance接口,两次获取SingleTon类的实例
SingleTon singleTonOne = SingleTon.getInstance();
SingleTon singleTonTwo = SingleTon.getInstance();
// 打印两次获取的实例的内存地址
System.out.println(singleTonOne);
System.out.println(singleTonTwo);
}
}
class SingleTon{
// SingleTon静态属性,初始化时new一个对象(类中能使用该类声明的private方法)
private static SingleTon singleTon = new SingleTon();
// 构造器私有了,外部不能new对象(保证了虚拟机中只能有一个实例)
private SingleTon(){
}
// 对外展示访问SingleTon实例的接口(保证了全局访问的都是SingleTon类的同一个实例)
public static SingleTon getInstance() {
return singleTon;
}
}
该方式的特点就是在加载
类的时候就new一个唯一的实例
,以后直接通过相应的API获取,继而保证了实例唯一,并且全局访问同一个实例。提高了使用效率,但是可能出现资源浪费的情况,假设整个程序运行过程中都没访问这个实例,那不是白白生成了一个实例。。。
2、懒汉式
同样将类的构造器私有化
,但是对外展示的获取实例的API智能生成实例,如果已经生成过了实例,直接放回之前的示例,否则生成一个新的实例。
public class SingleTonTest {
public static void main(String[] args) {
// 通过SingleTon.getInstance接口,两次获取SingleTon类的实例
SingleTon singleTonOne = SingleTon.getInstance();
SingleTon singleTonTwo = SingleTon.getInstance();
// 打印两次获取的实例的内存地址
System.out.println(singleTonOne);
System.out.println(singleTonTwo);
}
}
class SingleTon{
// SingleTon静态属性,指向生成的SingleTon实例
private static SingleTon singleTon = null;
// 构造器私有了,外部不能new对象(保证了虚拟机中只能有一个实例)
private SingleTon(){
}
// 对外展示访问SingleTon实例的接口(保证了全局访问的都是SingleTon类的同一个实例)
// 可能存在线程不安全的情况(多个线程同时调用getInstance,此时可能生成多个实例),使用synchronized关键字修饰,默认是SingleTon.class锁
public static synchronized SingleTon getInstance() {
if (singleTon == null) {
// 为null就new一个实例
singleTon = new SingleTon();
}
return singleTon;
}
}
懒汉式
中的懒
字,就是由 先判断是否生成过实例,再考虑是否生成实例而来。这种方式解决了饿汉式可能浪费资源的问题,但是降低了使用效率,因为每次都需要加锁、判断是否为null、释放锁。
四、总结
单例模式
是众多设计模式中较为简单的一种,核心思想是保证某类有且只用一个实例对象,并且需要保证全局都只能访问这个唯一的实例。
经典的例子就是GUI中的窗口,同一个窗口类一般只能有一个对象。不过博主个人认为,类中的static属性是单例思想的一种特殊运用,因为它保证了在本类中所用的对象访问同一个static属性
(大家看看就行,别当真😂😂😂)。
资料参考:
《大话设计模式》程杰著 单例模式