2.2单例模式
单例模式运用的可能比其他几种简单,通俗点理解就是,我这个对象只能存在一个。
问题
-
保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。
它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。
注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
客户端甚至可能没有意识到它们一直都在使用同一个对象。
-
为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。
和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。
还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例。
解决方案
那我们怎么保证只实例化出一个对象呢?new一个出来之后怎么保证不让下一次new出来这个对象呢?
在java的运用中,出现了很多运用到单例模式的例子,比如:线程池、缓存、对话框等等
比如在我们的Windows电脑上的注册表,如果存在多个我们并不是那么好管理。
关键的来了,我们怎么获取到只创建一个对象呢?
public MyClass{
private MyClass(){}
}
额。。。这样好像是可以防止其他对象实例化,但是怎么实例化出第一个对象呢?
我们可以通过其中的静态方法获取!
public MyClass{
private MyClass(){}
public static MyClass getInstance(){
return new MyClass();
}
}
嗯。。。这样就可以了,但是还差点什么东西?
public MyClass{
private static MyClass myClass;
private MyClass(){}
public static MyClass getInstance(){
if(myClass == null){
myClass = new MyClass();
}
return myClass;
}
}
!!!这样好像就做到了,好像还差一点!
public Singleton{
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
这样好像是解决了,但是在高并发的环境下,这样好像又不太行?
- 解决办法就是给该方法加锁
public Singleton{
private static Singleton singleton;
private Singleton(){}
//加锁
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
但是新的问题又来了,并行不是会降低性能么?
- 如果getInstance() 的性能对应用程序要求不高,那就什么都别做
- 如果很需要的话,而不用延迟实例化的做法
public Singleton{
//保证jvm在加载这个对象时马上创建该对象的实例。
private static Singleton singleton = new Singleton();
private Singleton(){}
//加锁
public static synchronized Singleton getInstance(){
return singleton;
}
}
- 加双重锁,保证在使用中减少同步
public Singleton{
private volatile static Singleton singleton;
private Singleton(){}
//加锁
public static Singleton getInstance(){
if(singleton == null){
synchronized(this){
singleton = new Singleton();
}
}
return singleton;
}
}
单例模式结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OMaMe3m2-1677143883407)(null)]
-
单例 (Singleton) 类声明了一个名为
getInstance
获取实例的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端 (Client) 代码隐藏。 调用
获取实例
方法必须是获取单例对象的唯一方式。
单例模式适合应用场景
如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。
单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。
如果你需要更加严格地控制全局变量, 可以使用单例模式。
单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。
请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例
方法, 即 getInstance 中的代码即可实现。
实现方式
- 在类中添加一个私有静态成员变量用于保存单例实例。
- 声明一个公有静态构建方法用于获取单例实例。
- 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
- 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
- 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。
单例模式优缺点
-
你可以保证一个类只有一个实例。
-
你获得了一个指向该实例的全局访问节点。
-
仅在首次请求单例对象时对其进行初始化。
-
违反了单一职责原则。 该模式同时解决了两个问题。
-
单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
-
该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
-
单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。
与其他模式的关系
- 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
- 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
- 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
- 单例对象可以是可变的。 享元对象是不可变的。
- 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。
参考:First Head设计模式、设计模式