Java设计模式持续更新中
模式总览
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象) 适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
一、创建型
1、单例设计模式(Singleton)
1.1 说明
确保一个类只有一个实例,并且提供了该实例的全局访问点
1.2 实现说明
使用一个私有的构造函数、一个私有静态变量以及一个公有静态函数返回唯一的私有静态变量。
1.3 实现
1.3.1 懒汉式-线程不安全
uniqueInstance 被延迟实例化了,调用 **getUniqueInstance() **时才会被加载,这样做的好处就是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
如果在高并发的情况下,如果有多个请求同时进入了
if (uniqueInstance == null)
,并且此时的uniqueInstance
为空,那么就会导致uniqueInstance = new Singleton();
会被实例化多次从而导致获取到脏数据的可能。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
1.3.2 懒汉式-线程安全
对当前这个方法进行加锁(
synchronized
),那么一个时间点只有一个线程能够进入该方法,避免导致多次实例化uniqueInstance
,但是当一个线程进入之后,其他的线程就必须在等待这个线程结束,那么就会导致线程阻塞时间过长,影响性能,不推荐使用
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
1.3.3 饿汉式-线程安全
线程不安全就是是由于uniqueInstance
被实例化多次,采取直接实例化uniqueInstance
可以避免这个问题,但是也会带来一个问题,就是没有延迟加载带来的节约资源的好处。
public static Singleton uniqueInstance = new Singleton();
1.3.4 双重校验锁-线程安全
如果我们既要拥有懒汉式的延迟加载特性也要饿汉式线程安全的特性,那么我们应该如何实现呢?
uniqueInstance
只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance
没有被实例化时,才需要进行加锁。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
考虑如下问题:
如果两个线程同时进入了if
语句并且未true
,即使有锁但是还是会同时执行uniqueInstance = new Singleton();
这条语句,那么就会进行两次实例化。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
所以就有必要进行双重校验锁,也就是需要使用两个 if 语句:第一个 if 语句用来避免 uniqueInstance
已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,所以只能有一个线程进入,就不会出现 uniqueInstance == null
时两个线程同时进行实例化操作。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
uniqueInstance
采用 [volatile](#1.3.4 双重校验锁-线程安全) 关键字修饰也是很有必要的, uniqueInstance = new Singleton()
; 这段代码其实是分为三步执行:
- 为
uniqueInstance
分配内存空间 - 初始化
uniqueInstance
- 将
uniqueInstance
指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance()
后发现 uniqueInstance
不为空,因此返回 uniqueInstance
,但此时 uniqueInstance
还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
1.3.5 静态内部类实现
如果不想使用 volatile
和双重检验锁的话,那么就可以使用 静态内部类进行实现。
根据静态数据的原理就是:随着java线程的执行,被static
修饰的属性或者变量会加载进静态池中,整个加载次数只有这一次。
只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
1.3.6 枚举实现
该实现可以防止反射攻击。在其他实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。
该实现在多次序列化和序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}