Java设计模式之创建者模式详解
作者:刘婉晴
前言
创建者模式,顾名思义,就是提供友好的创建对象的方式 ,对象都是 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 方法 。(注意深浅克隆的区别)
其实,按照我对原型模式的理解,就是新建了一个模板。然后,我们不断去克隆这个模版,更改模版中的一些值。
到此这篇关于Java设计模式之创建者模式详解的文章就介绍到这了,更多相关Java创建者模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!