Skip to content

创建型

design-create

单例模式

保证一个类中只有一个实例。

饿汉式

特点:类创建时创建对象,节省时间,占用内存,以空间换时间

  1. 静态变量实现

    类加载时创建对象,节省时间,占用内存,以空间换时间推荐使用,但是比较浪费空间。

    java
    		/**
         * 类加载时创建对象,节省时间,占用内存,以空间换时间
         */
        private final static SingletonHungryOne INSTANCE = new SingletonHungryOne();
    
        public static SingletonHungryOne getInstance() {
            return INSTANCE;
        }
  2. 静态代码块实现

    类似静态变量实现,静态代码块创建对象,在类加载完成之后,静态变量加载之后加载,节省时间,占用内存,以空间换时间。

    java
    		/**
         * 静态代码块创建对象,在类加载完成之后,静态变量加载之后加载,节省时间,占用内存,以空间换时间
         */
        private final static SingletonHungryTwo INSTANCE;
    
        static {
            INSTANCE = new SingletonHungryTwo();
        }
    
        public static SingletonHungryTwo getInstance() {
            return INSTANCE;
        }

懒汉式

特点:懒加载,以时间换空间

  1. 懒加载实现

    在获取对象实例的时候创建对象,实现懒加载。

    java
    		private static SingletonLazyOne INSTANCE;
    
        /**
         * 懒加载,判断对象为空时则创建,以时间换空间
         */
        public static SingletonLazyOne getInstance() {
            if (Objects.isNull(INSTANCE)) {
                INSTANCE = new SingletonLazyOne();
            }
            return INSTANCE;
        }

    但是在多线程情况下可能会导致多个实例的产生。比如一个线程进入了if (Objects.isNull(INSTANCE))判断条件里时,另一个线程也进入。这样导致在对象创建之前有两个线程同时满足了对象创建对象的条件,进而导致多个实例的产生。实际开发中,不推荐使用这种方式

  2. 同步方法实现

    在基本方法实现的基础上,增加同步方法,解决线程不安全的问题。

    java
    		private static SingletonLazyTwo INSTANCE;
    
        /**
         * 懒加载,对象为空时则创建,以时间换空间
         * 方法同步,保证线程安全,但是效率低下
         */
        public static synchronized SingletonLazyTwo getInstance() {
            if (Objects.isNull(INSTANCE)) {
                INSTANCE = new SingletonLazyTwo();
            }
            return INSTANCE;
        }

    虽然保证了线程安全,但是效率低下。每个线程获取该类实例时,调用方法都要进行同步。方法级别的同步效率太低。实际开发中,不推荐使用这种方式

  3. 同步代码块实现

    同步代码块,解决线程不安全和同步方法效率低下的问题。

    java
    		private static SingletonLazyThree INSTANCE;
    
        /**
         * 懒加载,对象为空时则创建,以时间换空间
         */
        public static  SingletonLazyThree getInstance() {
            if (Objects.isNull(INSTANCE)) {
                //同步代码块
                synchronized (SingletonLazyThree.class){
                    INSTANCE = new SingletonLazyThree();
                }
            }
            return INSTANCE;
        }

但是实际并没有解决线程安全问题,和普通饿汉式一样,在多线程环境下可能会创建出多个实例。实际开发中,不推荐使用这种方式

  1. 双重检查锁实现(推荐使用,延迟加载,线程安全,效率高)

    针对同步代码块在多线程情况出现多个实例的情况,可使用双重检查锁解决,来保证了单个实例的创建。

    java
    		/**
         * 使用volatile防止多线程环境下出现指令重排问题的发生。
         */
        private volatile static SingletonLazyFour INSTANCE;
    
        /**
         * 双重检查锁
         */
        public static SingletonLazyFour getInstance() {
            //第一次检查
            if (Objects.isNull(INSTANCE)) {
                //同步代码块
                synchronized (SingletonLazyFour.class){
                    //同步代码块内部检查
                    if(Objects.isNull(INSTANCE)){
                        INSTANCE = new SingletonLazyFour();
                    }
                }
            }
            return INSTANCE;
        }

    使用双重检查锁在同步代码块内部进行判断可有效解决线程安全问题,而同步代码块比同步方法锁粒度小,效率更高,所以实际开发中,推荐这种方式

静态内部类

静态内部类,在外部类被加载的时候不会立即加载。只有在调用静态内部类里的参数或方法时,才会被加载。达到了对象懒加载的效果。

不管是懒汉式还是饿汉式,由于对象是静态的,在第一次调用类的时候,无论对象是否初始化,该对象都会被加载。

在类加载的时候,是线程安全的,达到了线程安全的效果。推荐使用

java
		/**
     * 静态内部类,在外部类被加载的时候不会立即加载。
     * 只有在调用该类里的参数或方法时,才会被加载。达到了懒加载的效果。
     * 在类加载的时候,是线程安全的。达到了线程安全的效果。
     */
    private static class SingletonStaticClass {
        private static final SingletonStatic SINGLETONSTATIC = new SingletonStatic();
    }

    public static SingletonStatic getInstance() {
        return SingletonStaticClass.SINGLETONSTATIC;
    }

枚举类

每一个枚举类型及其定义的枚举变量在JVM中都是唯一的特性,保证了枚举对象的唯一性。 枚举可以有效避免序列化时出现的新实例的情况。

java
public class SingletonEnumImpl {

    /**
     * 每一个枚举类型及其定义的枚举变量在JVM中都是唯一的特性,保证了枚举对象的唯一性。
     */

    private enum SingletonEnum {
        /**
         * 唯一实例
         */
        INSTNACE;

        private SingletonEnumImpl singletonEnumImpl;

        private SingletonEnum() {
            singletonEnumImpl = new SingletonEnumImpl();
        }

        private SingletonEnumImpl getSingletonEnumImpl() {
            return singletonEnumImpl;
        }

    }

    public static SingletonEnumImpl getInstance() {
        return SingletonEnum.INSTNACE.getSingletonEnumImpl();
    }

    public static void main(String[] args) {
        SingletonEnumImpl instanceOne = SingletonEnumImpl.getInstance();
        SingletonEnumImpl instanceTwo = SingletonEnumImpl.getInstance();
        System.out.println(instanceOne == instanceTwo);
    }

}

//output true

单例模式的序列化问题

反序列化时,JVM 会通过反射调用无参构造器创建新对象,绕过私有构造器。

枚举天然防序列化破坏,JVM 保证枚举实例的唯一性。

源码中的应用

  • Launcher类里的ExtClassLoader类使用了懒汉式双重检查锁。

    java
    			public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
                    // 懒汉式双重加载
      				if (instance == null) {
                    Class var0 = Launcher.ExtClassLoader.class;
                    synchronized(Launcher.ExtClassLoader.class) {
                        if (instance == null) {
                          	//调用创建方法
                            instance = createExtClassLoader();
                        }
                    }
                }
    
                return instance;
            }

工厂模式

将实例化对象的代码提取出来,放到统一的工厂类进行统一维护和管理。将对象的创建和使用的过程分开。 达到和逻辑处理类之间的解耦合,从而提高项目的扩展和维护性。

简单工厂模式

特点:由工厂对象来决定创建出哪一种产品类。

实现:创建一个工厂类,来进行实例对象的创建。

工厂模式

特点:将工厂类抽象为一个抽象类或者接口。由其实现类完成创建对象的功能。只能创建一个产品实例。

实现:创建一个抽象工厂类,提供创建对象的方法,由其子类工厂实现创建对象的逻辑。

抽象工厂模式

特点:和工厂模式相比,有多个抽象产品类实例。

实现:创建一个抽象工厂类,提供多个创建产品的方法,由其子类工厂去实现创建各个产品对象的逻辑

原型模式

特点和原理

特点

通过克隆实例对象,来创建新的对象。

原理

Java中Object是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个java对象复制一份。需要java类实现接口Cloneable,表示该类拥有复制类的能力。

浅拷贝和深拷贝

  1. 浅拷贝
    • 浅拷贝是通过默认的clone()方法来实现。

    • 数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递。

    • 数据类型是引用数据类型的成员变量,浅拷贝会进行引用传递,将引用类型成员变量的地址值复制一份给新的对象。

      实际上两个对象的引用类型变量都指向同一个实例。在这种情况下修改该成员变量会影响到另一个对象的该成员变量值。

  2. 深拷贝
    • 深拷贝可通过修改克隆方法和通过对象序列化两种方式实现。推荐使用对象序列化。
    • 数据类型是基本类型或引用类型,深拷贝会对整个对象进行拷贝,即拷贝出一个新的对象。

建造者模式

可以实现对象的链式调用。

  • lombok 的 @Builder

    java
    public class BuildExample {
    
        private String id;
        private String name;
        private int age;
    
        public BuildExample() {
        }
    
        public BuildExample(String id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public static BuildExample builder() {
            return new BuildExample();
        }
    
        public BuildExample build() {
            return new BuildExample(this.id, this.name, this.age);
        }
    
        public BuildExample id(String id) {
            this.id = id;
            return this;
        }
    
        public BuildExample name(String name) {
            this.name = name;
            return this;
        }
    
        public BuildExample age(int age) {
            this.age = age;
            return this;
        }
    
        @Override
        public String toString() {
            return "BuildExample{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        public static void main(String[] args) {
            BuildExample buildExample = BuildExample.builder()
                    .id("123")
                    .name("test")
                    .age(110)
                    .build();
            System.out.println(buildExample);
        }
    
    }

源码地址

Albert.Yang/design-patterns-practice