前言
设计模式原则
开闭原则 | 对扩展开放,对修改关闭 |
单一职责原则 | 类职责应尽可能单一 |
里氏替代原则 | 只要父类能出现的地方,子类就可以出现 |
依赖倒置原则 | 细节应该依赖抽象 |
接口隔离原则 | 每个接口中不存在子类用不到却必须实现的方法 |
迪米特法则 / 最少知识原则 | 一个对象对其依赖应尽可能少的了解 |
合成复用原则 | 优先使用组合 / 聚合,其次考虑继承 |
概述
将对象的创建与使用分离
单例模式 | 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例 |
工厂方法模式 | 定义一个用于创建产品的接口,由子类决定生产什么产品 |
抽象工厂模式 | 提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品 |
建造者模式 | 将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象 |
原型模式> | 将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例 |
单例模式
定义
某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例
主要特点
-
单例类只有一个实例
-
单例对象必须由单例类自行创建
-
单例类对外提供一个访问该单例的全局访问点
八种写法
线程安全 | 懒加载 | 可用性 | |
---|---|---|---|
饿汉式——静态常量 | 是 | 是 | 可用 |
饿汉式——静态代码块 | 是 | 是 | 可用 |
懒汉式——线程不安全 | 否 | 是 | 不可用 |
懒汉式——同步方法 | 是 | 是 | 不推荐 |
懒汉式——同步代码块 | 否 | 是 | 不可用 |
双检锁 | 是 | 是 | 推荐 |
静态内部类 | 是 | 是 | 推荐 |
枚举 | 是 | 否 | 推荐 |
常见可用写法
(1)饿汉式——静态常量
// 饿汉式——静态常量
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
(2)饿汉式——静态代码块
// 饿汉式——静态代码块
public class Singleton {
private static Singleton instance;
static { instance = new Singleton(); }
private Singleton() {}
public static Singleton getInstance() { return instance; }
}
(3)双检锁
// 双检锁
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
为什么要加volatile
关键字?
volatile 是为了禁止重排序,因为同步代码块内在实例化对象的时候可能发生重排序,导致多线程环境下获取一个不正确的对象
构造方法可能是一个非原子操作,编译后会生成多条字节码指令。由于java的指令重排序可能会先执行instance的赋值操作,该操作实际只是在内存中开辟一块存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却可能还没有执行,别的线程拿到的就可能是一个不为空但是也不完整的instance对象
每个线程都有自己的内存空间,使用 volatile 会让线程内存中的instance变化立即刷新到主内存中,保证别的线程从主内存中读取到的instance是正确的
(4)静态内部类
无论是静态内部类还是非静态内部类,都是延迟加载的
// 静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() { return SingletonHolder.INSTANCE; }
}
(5)枚举
// 枚举
public enum Singleton {
INSTANCE;
// 默认私有化构造方法
private Singleton() {}
// 无需getInstance()方法
public static Singleton getInstance() { return INSTANCE; }
// 成员方法
public void doSth() {}
}
怎么防止单例模式被破坏?
在Java中创建对象的方式有4种。而除了new
, 后面的三种方式都可以破坏单例模式
new
- 反射
- 克隆
- 反序列化
(1)防止反射破坏
可以使用一个flag
,在第二次实例化时抛出异常
(2)防止克隆破坏
重写的clone()
方法直接返回INSTANCE
对象
(3)防止反序列化破坏
定义readResolve()
方法(原理请查看序列化和反序列化的对单例破坏的防止及其原理)
举个例子
// 饿汉式——静态常量
public class Singleton implements Cloneable, Serializable {
private static final Singleton INSTANCE = new Singleton();
private static boolean flag = false;
private Singleton() {
synchronized (Singleton.class) {
// 防止反射破坏
if (!flag) {
flag = !flag;
} else {
throw new RuntimeException();
}
}
}
public static Singleton getInstance() { return INSTANCE; }
// 防止克隆破坏
@Override
public Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
// 防止反序列化破坏
protected Object readResolve() {
return INSTANCE;
}
}
工厂方法模式
定义
定义一个用于创建产品的接口,由子类决定生产什么产品
UML图
主要角色
-
抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法
newProduct()
来创建产品 -
具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
-
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应
为什么简单工厂模式、静态工厂模式不属于GoF的23种经典设计模式?
如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”或“静态工厂模式”
它的缺点是增加新产品时会违背“开闭原则”
抽象工厂模式
定义
提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品
UML图
主要角色
-
抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法
newProduct()
,可以创建多个不同等级的产品 -
具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
-
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
抽象工厂模式相比工厂方法模式有哪些优缺点?
工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品
系统中有多于一个的产品族,而每次只使用其中某一产品族,可以很方便的扩展新的产品族
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改
举个栗子
建造者模式
定义
将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象
UML图
主要角色
- 指挥者:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息
- 抽象建造者:它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法
getResult()
- 具体建造者:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法
- 产品:它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件
指挥者的作用
指挥者可以通过组合建造不同组件的顺序建造出不同的产品。比如
// 第一种产品
Product construct() {
this.builder.buildComponentA();
this.builder.buildComponentB();
this.builder.buildComponentC();
return this.builder.getResult();
}
// 第二种产品
Product construct() {
this.builder.buildComponentA();
this.builder.buildComponentC();
this.builder.buildComponentB();
return this.builder.getResult();
}
// 第三种产品
Product construct() {
this.builder.buildComponentB();
this.builder.buildComponentA();
this.builder.buildComponentC();
return this.builder.getResult();
}
和工厂模式的区别
关注点不同。两者可以结合使用更佳
-
建造者模式更加关注零部件的组装
-
工厂模式更加关注零部件的创建
原型模式
定义
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例
主要角色
-
抽象原型:规定了具体原型对象必须实现的接口
-
具体原型:实现抽象原型类的 clone() 方法,它是可被复制的对象
举个例子
在Java中所以类均继承自Object类,实现Cloneable
接口重写clone()
方法并将其访问权限提升至public
即可
1、浅拷贝
调用父类的clone()
方法即可
public class Product implements Cloneable {
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2、深拷贝
浅拷贝后还需对引用类型(特指自定义类型)的属性进行克隆( 如果未直接继承Object,中间还有其它继承层次,则每一层都需要实现Cloneable
接口并覆盖clone()
方法
public class Person implements Cloneable {
private String name;
private Integer age;
private Product product;
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
person.product = (Product) person.product.clone();
return person;
}
}