1.什么是设计模式
设计模式是一种被广泛认可的、可复用的解决方案,用于在软件开发中解决常见的问题。它们是对软件设计中常见问题的总结与提炼,提供了一套可遵循的标准和最佳实践,帮助开发人员构建高效、可维护和灵活的系统。
2.设计模式的分类
设计模式通常分为三大类:
1.创建型模式:关注如何创建对象,主要解决对象创建过程中的问题。常见的创建型模式包括:
- 单例模式:确保一个类只有一个实例。
- 工厂模式:提供创建对象的接口,允许子类决定实例化哪一个类。
- 抽象工厂模式:提供一个接口,用于创建一系列相关或相互依赖的对象。
- 结构型模式:关注如何将类或对象组合成更大的结构,主要解决类与对象之间的关系。常见的结构型模式包括:
2.适配器模式:将一个类的接口转换成客户端所期望的另一种接口。
- 装饰者模式:动态地给对象添加额外的功能。
- 代理模式:为其他对象提供一种代理,以控制对这个对象的访问。
3.行为型模式:关注对象之间的通信和责任分配,主要解决对象的交互与职责。常见的行为型模式包括:
- 观察者模式:定义对象间的一种一对多的依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。
- 策略模式:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。
- 命令模式:将请求封装成对象,从而使你可以用不同的请求对客户进行参数化。
3.单例设计模式
单例设计模式(Singleton Pattern)是一种常见的设计模式,属于创建型模式。所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法(静态方法)。这种模式常用于需要控制访问共享资源的场景,例如数据库连接、配置管理等。
3.1 特点
- 唯一性:类只能有一个实例。
- 全局访问:提供一个静态方法获取该实例。
- 延迟加载:实例在首次使用时创建,避免不必要的内存占用。
3.2 实现方式
单例模式可以通过多种方式实现,下面介绍几种常用的方法。
3.2.1 懒汉式(Lazy Initialization)
- 线程不安全方式(不推荐)
//懒汉式(线程不安全)
class LazySingleTon {// 构造器私有化,外部无法通过new创建对象private LazySingleTon() {}// 本类内部创建对象实例private static LazySingleTon instance;// 提供一个公有的静态方法供外部使用,返回实例对象public static LazySingleTon getInstance() {if(instance == null) {instance = new LazySingleTon();}return instance;}
}
优点: 起到了Lazy Loading的效果,但是只能在单线程下使用。
缺点: 多线程下可能出现多个线程同时获取到instance 为null的情况,这时便会产生多个实例。
- 线程安全方式(推荐)
class LazySingleTon{// 私有构造器,防止外部通过 new 创建对象private LazySingleTon() {}// 静态内部类,只有在第一次调用 getInstance 方法时才会被加载private static class Holder {private static final LazySingleTon INSTANCE = new LazySingleTon();}// 提供公有的静态方法供外部使用,返回实例对象public static LazySingleTon getInstance() {return Holder.INSTANCE; // 返回静态内部类中的实例}
}
优点: 由于静态内部类的加载是懒加载且由 JVM 保证其线程安全,Java 的类加载机制是由 JVM 负责的,类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入,被阻塞,直到初始化完成。这防止了多个线程同时创建类实例的情况。
缺点: 如果 SingleTon 类加载的时机与应用程序启动时间不一致,可能导致首次访问的延迟。
3.2.2 饿汉式(Eager Initialization)
public class EagerSingleton {// 在类加载时就实例化单例对象,确保在静态上下文中只有一个实例private static final EagerSingleton instance = new EagerSingleton();// 私有构造器,防止外部通过 new 创建对象private EagerSingleton() {}// 提供公有的静态方法供外部获取实例public static EagerSingleton getInstance() {return instance;}
}
优点: 实现简单,无需处理线程安全。
缺点: 在应用启动时就创建实例,如果从始至终从未使用过这个实例,则会造成内存的浪费。
3.2.3 双重检查锁(Double-Check Locking)
public class DoubleCheckSingleton {private volatile static DoubleCheckSingleton instance;// 私有构造函数,防止外部实例化private DoubleCheckSingleton() {}public static DoubleCheckSingleton getInstance() {//先判断对象是否已经实例过,没有实例化过才进入加锁代码if (instance == null) { synchronized (DoubleCheckSingleton.class) {//类对象加锁if (instance == null) { instance = new DoubleCheckSingleton();}}}return instance;}
}
注意: instance 采用 volatile 关键字修饰是很有必要的, instance = new DoubleCheckSingleton()其实是分为三步执行:
1.为 instance分配内存空间
2.初始化 instance
3.将 instance指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 instance不为空,因此返回 instance,但此时 instance还未被初始化。
优点: 线程安全、延迟加载、效率较高,在实际开发中,推荐使用这种单例设计模式。
3.2.4 枚举单例
public enum EnumSingleton {INSTANCE;// 可以添加其他方法或字段public void someMethod() {// 实现具体的逻辑}
}
优点:
- 使用 enum 定义的 INSTANCE 是唯一的。Java 的枚举类型在 JVM 中保证了单例,因为每个枚举值在类加载时就被创建,且不会被再创建
- 枚举类型自动提供序列化机制,避免了传统单例模式中需要处理的序列化问题。即使通过反序列化,返回的也是同一个实例。
- 枚举实例的创建是线程安全的,不需要显式的同步。
4.总结
单例设计模式适用于当需要控制资源共享(如数据库连接、线程池)、应用程序需要一个全局访问点或需要提供全局状态或配置信息的情况下使用。
单例设计模式通过控制实例化过程,为系统提供了一个灵活的解决方案,确保资源有效利用和访问一致性。不同的实现方式各有优缺点,开发者可以根据具体需求选择合适的实现。