意义:主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码
单例模式
概念:一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式 应用场景:解决资源访问冲突、创建全局为一类:配置类
单例的5种实现
饿汉式
饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例(在真正用到的时候,再创建实例)。
public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static final IdGenerator instance = new IdGenerator(); private IdGenerator() {} //构造方法 public static IdGenerator getInstance() { return instance; } //实例方法 public long getId() { return id.incrementAndGet(); }//实例方法 }
懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈
public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private IdGenerator() {} public static synchronized IdGenerator getInstance() { if (instance == null) { instance = new IdGenerator(); } return instance; }//实例方法 public long getId() { return id.incrementAndGet(); }//实例方法 }
双重检测
双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题
public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private IdGenerator() {} public static IdGenerator getInstance() { if (instance == null) { synchronized(IdGenerator.class) { // 此处为类级别的锁 if (instance == null) { instance = new IdGenerator(); } } } return instance; } public long getId() { return id.incrementAndGet(); } }
静态内部类
利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单
public class IdGenerator { private AtomicLong id = new AtomicLong(0); private IdGenerator() {} private static class SingletonHolder{ private static final IdGenerator instance = new IdGenerator(); } public static IdGenerator getInstance() { return SingletonHolder.instance; } public long getId() { return id.incrementAndGet(); } }
枚举
最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性
public enum IdGenerator { INSTANCE; private AtomicLong id = new AtomicLong(0); public long getId() { return id.incrementAndGet(); } }
单例存在的问题
单例对 OOP 特性的支持不友好 单例会隐藏类之间的依赖关系 单例对代码的扩展性不友好 单例对代码的可测试性不友好 单例不支持有参数的构造函数
单例的替代解决方案
静态方法/通过工厂模式、IOC 容器(比如 Spring IOC 容器)来保证
单例模式的唯一性
单例类中对象的唯一性的作用范围是“进程唯一”的 进程唯一指进程内唯一,进程间不唯一,线程内和线程间都唯一
如何实现线程唯一的单例
通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象/java ThreadLocal 并发工具类
如何实现集群环境下的单例
集群唯一”指的是进程内唯一、进程间也唯一 把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,需要显式地将对象从内存中删除,并且释放对对象的加锁
如何实现一个多例模式
“多例”指的就是一个类可以创建多个对象,但是个数是有限制的,比如只能创建 3 个对象。 实现:通过一个Map 来存储对象类型和对象之间的对应关系,来控制对象的个数。
工厂模式
用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象;封装对象的创建过程,将对象的创建和使用相分离 工厂模式的应用场景:创建逻辑比较复杂,“大工程”的时候 类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else创建对象的代码抽离出来,放到工厂类中 单个对象本身的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中
简单工厂
应用场景:每个对象的创建逻辑都比较简单的时候 将多个对象的创建逻辑放到一个工厂类中 实现方式 每次都返回新创建的对象 每次都返回同一个事先创建好的对象
工厂方法
应用场景:每个对象的创建逻辑都比较复杂的时候/单个对象本身的创建逻辑就比较复杂 为了避免设计一个过于庞大的简单工厂类,使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中
抽象工厂
判断要不要使用工厂模式的标准
封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明 代码复用:创建代码抽离到独立的工厂类之后可以复用 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁
DI容器
概念:依赖注入框架,或者叫依赖注入容器 工厂模式 VS DI容器 DI 容器底层最基本的设计思路就是基于工厂模式的,相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象,处理的是更大的对象创建工程,负责的是整个应用中所有类对象的创建 一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建
核心功能
配置解析:将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。 对象创建:将所有类对象的创建都放到一个工厂类中完成 对象生命周期管理
实现
核心逻辑:配置文件解析,以及根据配置文件通过“反射”语法来创建对象 反射语法:能在程序运行的过程中,动态地加载类、创建对象,不需要事先在代码中写死要创建哪些对象 复习SpringDI、Ioc概念
建造者模式
用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
工厂模式 VS 建造者模式
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象;类比利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉 建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象;类比通过建造者模式根据用户选择的不同配料来制作披萨
应用场景
类的必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题 类的属性之间有一定的依赖关系或者约束条件 需要创建不可变对象
原型模式
概念:如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象。基于原型来创建对象的方式就叫作原型设计模式,简称原型模式
两种实现方法
浅拷贝:浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象;主要应用于要拷贝的对象是不可变对象
深拷贝2种实现方式
递归拷贝对象、对象的引用对象以及引用对象的引用对象……直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止 先将对象序列化,然后再反序列化成新的对象
深拷贝VS浅拷贝
浅拷贝得到的对象跟原始对象(共享数据),而深拷贝得到的是一份完完全全独立的对象 深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间 除非操作非常耗时,比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝
总结
单例设计模式:一个类只允许创建一个对象(或者实例)
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象;类比利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉 ;
建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象;类比通过建造者模式根据用户选择的不同配料来制作披萨
基于原型来创建对象的方式就叫作原型设计模式