Java设计模式之创建者模式详解

 更新时间:2023年08月18日 08:30:46   作者:刘婉晴  
这篇文章主要介绍了Java设计模式之创建者模式详解,创建者模式,顾名思义,就是提供友好的创建对象的方式 ,对象都是 new 出来的,但是在一些情况下,这种方式不是很友好,首先,它不够直观,需要的朋友可以参考下

前言

创建者模式,顾名思义,就是提供友好的创建对象的方式 ,对象都是 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创建者模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis-spring:@MapperScan注解的使用

    mybatis-spring:@MapperScan注解的使用

    这篇文章主要介绍了mybatis-spring:@MapperScan注解的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • java获取request中的参数以及java解析URL问号后的参数

    java获取request中的参数以及java解析URL问号后的参数

    这篇文章主要介绍了java获取request中的参数以及java解析URL问号后的参数问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 如何使用Spring自定义Xml标签

    如何使用Spring自定义Xml标签

    要实现自定义的xml配置,需要有两个默认spring配置文件来支持。一个是spring.schemas,一个是spring.handlers,前者是为了验证你自定义的xml配置文件是否符合你的格式要求,后者是告诉spring该如何来解析你自定义的配置文件。本文将介绍如何使用Spring自定义Xml标签
    2021-06-06
  • 详解Eclipse提交项目到GitHub以及解决代码冲突

    详解Eclipse提交项目到GitHub以及解决代码冲突

    这篇文章主要介绍了详解Eclipse提交项目到GitHub以及解决代码冲突,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • MyBatis+Calcite实现多数据库SQL自动适配的详细指南

    MyBatis+Calcite实现多数据库SQL自动适配的详细指南

    在当今企业IT环境中,数据库异构性已成为常态,根据DB-Engines最新调研,超过78%的企业同时使用两种以上数据库系统,所以本文就来为大家介绍一下如何基于MyBatis+Calcite实现多数据库SQL自动适配吧
    2025-04-04
  • 使用Spring AOP实现用户操作日志功能

    使用Spring AOP实现用户操作日志功能

    这篇文章主要介绍了使用Spring AOP实现了用户操作日志功能,功能实现需要一张记录日志的log表,结合示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • SpringBoot中Dozer的使用小结

    SpringBoot中Dozer的使用小结

    dozer是用来两个对象之间属性转换的工具,有了这个工具之后,我们将一个对象的所有属性值转给另一个对象时,就不需要再去写重复的set和get方法了,下面介绍下SpringBoot中Dozer的使用,感兴趣的朋友一起看看吧
    2022-03-03
  • Springcloud中Feign传递参数的过程解析

    Springcloud中Feign传递参数的过程解析

    这篇文章主要介绍了Springcloud中Feign传递参数的过程,单个参数的传值有两种方式,第一种使用@RequestParam/@PathVariable进行传值,传递多个参数:多个参数的传值可以使用多个@RequestParam来进行传参,需要的朋友可以参考下
    2023-09-09
  • SpringBoot中EasyExcel实现Excel文件的导入导出

    SpringBoot中EasyExcel实现Excel文件的导入导出

    这篇文章主要介绍了SpringBoot中EasyExcel实现Excel文件的导入导出,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Java编程接口回调一般用法代码解析

    Java编程接口回调一般用法代码解析

    本文的主要内容是同过实际代码向大家展示Java编程中接口回调的一般用法,具有一定参考价值,需要的朋友可以了解下
    2017-09-09

最新评论