创建者模式,顾名思义,就是提供友好的创建对象的方式 ,对象都是 new 出来的,但是在一些情况下,这种方式不是很友好,首先,它不够直观,其次,在一些情况下,这样创建的对象不满足要求。 —— 比如 : 当我们需要创建单例的对象,那我们就不能每次用的时候就重新 new, 这样很明显是不合理的 。
一、工厂模式
引入工厂类的原因 : 是因为我们需要两个或者两个以上的工厂生产对象,假如我们就需要一个对象的工厂,那就没必要引入工厂类了 。
1. 简单工厂模式
就一个工厂,它具有不同的方法,生产出不同的产品 。
2. 工厂模式
我们需要两个及以上的工厂,所以抽象出一个工厂接口,其他工厂实现类实现这个抽象接口,重写创建方法 。
3. 抽象工厂模式
涉及到产品族问题 , 调用的时候先选择工厂,再调用其方法,生成相应的产品。
举个🌰 : 工厂模式在 Spring 框架中的使用
Spring 框架通过反射机制实现工厂模式,从而降低了程序的耦合程度 。具体实现思路为 : 读取配置文件中相关值, 然后通过反射 Class.forName 得到该值对应的类,再通过 newInstance() 获取该类的实例返回 。
我们调用时只需要 : Factory.getXXX("我们定义在配置文件中的 Bean 名称")
二、单例模式
1. 饿汉式实现
私有构造方法 + 创建静态实例
public class Singleton {
// 私有构造方法
private Singleton() {};
// 创建私有静态实例
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
缺点 : 不想用 Singleton 实例时,它也生成了,因为是 static 的, 这个类被第一次使用时就会被生成 。
2. 饱汉式实现 —— 双重检查机制原理
私有构造方法 + volatile 修饰 + 双重判空,加锁
public class Singleton {
// 私有构造方法
private Singleton() {}
// valitale 修饰
private static volatile Singleton instance = null;
public static Singleton getInstance() {
// 双重检查是否为 null
if (instance == null) {
// 加锁
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 为什么要在实例化时加 synchronized 锁, 不是已经用 volatile 关键字修饰了吗 ?
因为 volatile 只可以保证变量的可见性 和 防止实例化时指令重排 ,它不能保证操作的原子性 。我们要求只能有一个线程去实例化,不能一堆线程一起进去实例化 。
- 不是已经有 synchronized 锁了吗,为什么还要 volatile 关键字修饰 ?
因为我们得保证 一个线程实例化 Singleton 后,其他线程可见,直接返回就行,所以我们需要 volatile 去保证 Singleton 在多个线程中的可见性 , 不被 JVM 缓存。
除此之外,它也通过防止对象创建时的指令重排,而使得线程安全 。
什么是指令重排 ?
创建对象时,先分配内存空间,然后实例指向该空间地址,然后才初始化 。
正常情况应该是, 分配内存空间 — 初始化 — 指向该空间地址
如果不用 volatile 修饰, 发生指令重排时, 创建对象的线程只是指向了空间地址,但是还没初始化,但是因为已经指向地址了,下一个线程在第一重 null 检查时,判断为非 null,直接返回了,但是返回的实例还没初始化 。—— 这是线程不安全的,所以需要 volatile 。
3. 嵌套类实现
利用嵌套类可以 访问外部类 的属性和方法 。在嵌套类中实例化 Singleton 。
public class Singleton {
private Singleton() {}
// 利用嵌套类可以访问外部类的属性和方法
private static class Holder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
4. 枚举实现 *
public enum Singleton {
INSTANCE;
}
据说被谷歌大佬作为实现单例的最佳实践 —— 即简单又安全
下面我们来一起简单看一下枚举为什么可以实现单例 ?
我们自定义的枚举类 默认 继承 Enum, Enum 的源码如下 :
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
// 枚举项的名字 , 程序员用 toString 方法而不是这个
private final String name;
public final String name() {
return name;
}
// 程序员用不到的,给使用枚举类型的 API 使用,比如 java.util.EnumSet
private final int ordinal;
public final int ordinal() {
return ordinal;
}
// 程序员用不到的,给编译器发出的代码,告诉编译器实例化这个枚举类
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
// 克隆枚举类抛出异常,这保证了枚举永远不会被克隆,这对于保持它们的 "单例 "状态是必要的。
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
// 返回这个枚举常量的枚举类型所对应的Class对象
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
我们定义的枚举类里面的每个枚举项都是一个 Enum, 这个 Enum 都是 static final 类型的,也就是说不容许被修改,初次之外我们的自定义的枚举类默认私有构造方法(体现在编译完之后),不能被程序员改成 public 的
我们再翻译一下,翻译成我觉得更好理解的
- 首先, 枚举类里的枚举项都是 枚举类 Enum 的实例,枚举帮我们自动处理了一些东西,简化了代码
- 其帮我们进行的操作就是对每个枚举项私有了构造方法,然后实例定义成 static final 类型的,就是相当于我们饿汉式的实现代码,其帮我们封装好了,就是其帮我们封装好了饿汉式的实现代码
- 除此之外,我们枚举类的实例只能有我们在枚举类里的那几个,对单例来说只有那一个,不容许再进行扩张实例 ,只能有那一个。
三、建造者模式
链式调用方法,实现对象的创建 。 —— 为什么优于 new ?
首先,一个设计模式是最佳实践是相对其使用场景说的。 建造者模式适用于要创建的对象非常复杂的情况,构造者模式让这个复杂类的装配变得按部就班又直观。
举个栗子🌰
MyBatis 中读取配置文件构建配置类,这个配置类超级大 。所以使用了建造者模式,把这个复杂类的构建拆分成了好多 builder,根据传入的参数的不同进行创建 。
四、原型模式
对于原型模式,首先我们需要记住的就是实现 Cloneable 接口,并且重写 clone 方法 。(注意深浅克隆的区别)
其实,按照我对原型模式的理解,就是新建了一个模板。然后,我们不断去克隆这个模版,更改模版中的一些值。