vlambda博客
学习文章列表

不得不会的23种Java设计模式(一)——单例模式

不得不会的23种设计模式(一)——单例模式

  • 为什么要学习设计模式

  • 设计模式遵循的原则

    • 单一职责原则

    • 开放封闭原则

    • 依赖倒转原则

    • 里氏代换原则

    • 迪米特法则

  • 设计模式的分类

  • 单例模式

    • 适用场景

    • 案例

    • 单例模式的分类

      • 饿汉式

      • 懒汉式

      • 双重检测锁

      • 静态内部类

      • 枚举方式


为什么要学习设计模式

想一想为什么要学习数据结构和算法,就想清楚了为什么要学习设计模式了
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

设计模式遵循的原则

单一职责原则

就一个类而言,应该仅有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受意想不到的破坏。

开放封闭原则

软件实体(类、模块、函数等等)应该可以扩展,但是不可以修改。
这个原则其实有两个特征,一个是“对于扩展时开放的”,另一个是“对于更改时封闭的”。

依赖倒转原则

抽象不应该依赖细节,细节应该依赖于抽闲。
依赖倒转原则:
A. 高层模块不应该依赖底层模块。两个都应该依赖抽象。
B. 抽象不应该依赖细节。细节应该依赖抽象。

里氏代换原则

子类必须能够代替它们的父类型。
因为有了这个原则,使得继承复用成为了可能,只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上添加新的行为。

迪米特法则

如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
1.迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行为就不要公开
2.类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说,信息的隐藏促进了软件的复用。

设计模式的分类

  • 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程

  • 结构型模式:把类或对象结合在一起形成一个更大的结构。

  • 行为型模式:类和对象如何交互,及划分责任和算法。

单例模式

保证在一个JVM中,该对象只有一个实例存在

适用场景

  • 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

  • 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

  • 有些类比如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

案例

package test19;

public class Singleton {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton instance = null;

/* 私有构造方法,防止被实例化 */
private Singleton() {
}

/* 静态方法,创建实例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
package test19;

public class Main {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
}
}

单例模式的分类

饿汉式

类初始化时创建单例,线程安全,适用于单例占内存小的场景。

package test19;

public class Singleton {
private static Singleton instance = new Singleton();

/* 私有构造方法,防止被实例化 */
private Singleton() {
}

/* 静态方法,创建实例 */
public static Singleton getInstance() {
return instance;
}
}

懒汉式

需要创建单例实例的时候再创建,需要考虑线程安全(性能不太好)

package test19;

public class Singleton {
private static Singleton instance = null;

/* 私有构造方法,防止被实例化 */
private Singleton() {
}

/* 静态方法,创建实例 */
public static synchronized Singleton getInstance() {
if(null == instance){
instance = new Singleton();
}
return instance;
}
}

双重检测锁

效率高(假如两个线程A和B,A执行了if(instance == null)语句,它会认为单例对象没有创建,此时线程切到B也执行了同样的语句,B也认为单例对象没有创建,然后两个对象依次执行同步代码块)

package test19;

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;
}
}

静态内部类

同时保证延迟加载和线程安全

package test19;

public class Singleton {
//静态内部类
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态方法,创建实例 */
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

枚举方式

除了线程安全和防止反射调用构造器,还提供了自动序列化机制,防止反序列化的时候创建新的对象。

package test19;

public enum Singleton {
instance;
}
package test19;

public class Main {
public static void main(String[] args) {
Singleton singleton = Singleton.instance;
Singleton singleton1 = Singleton.instance;
System.out.println(singleton == singleton1);
}
}