学习笔记-Java设计模式-创建型模式

Java设计原则&&模式学习笔记

说明

近期扫地生决定整合一下年初学习的设计模式,一来用于复习巩固,二来也希望可以把自己的整合与有需要的同学共勉。

扫地生在学习的过程中主要参考的是公众号“一角钱技术”的相关推文,同时也参考了下列几篇文章。对这些作者扫地生表示衷心的感谢。

参考文章1-Java设计原则

参考文章2-Java设计模式总结

参考文章3-23种设计模式速记

3、创建型模式

3.1 创建型模式1——单例模式(Singleton)

速记关键词:单实例

简述

定义:保证一个类只有一个实例,并且提供一个全局访问点。(防止过多创建消耗资源)

场景:线程池、数据库连接池

  1. 类加载机制
  2. 字节码知识
  3. JVM指令重排
  4. Java序列化机制

img

图片

单例模式有8种

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查(DCL)
  7. 静态内部类
  8. 枚举

实现方式:

1 饿汉式(静态常量)
  • 构造器私有化(防止new)
  • 类的内部创建对象
  • 只向外部暴露一个静态的公共方法(getInstance)
package top.saodisheng.designpattern.singleton.v1;

import org.junit.jupiter.api.Test;

/**
 * description:
 *  饿汉模式(静态变量)
 *  类加载到内存后,就实例化一个单例。JVM保证线程安全
 *  简单使用,推荐使用
 *  唯一缺点:不管用到与否,类装载时就完成实例化
 *  Class.forName("")
 *     (话说你不用的,你装载它干啥)
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    // 1. 构造器私有化(防止new)
    private Singleton () {
    }
    // 2. 本类内部创建对象
    private final static Singleton instance = new Singleton();

    // 3. 提供一个公有的静态方法,返回实例对象,提供给外部使用
    public static Singleton getInstance() {
        return instance;
    }

    @Test
    public void test1() {
        // 测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        // true
        System.out.println(instance1 == instance2);

        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
 }

image-20211106100451330

优缺点:

  • 优点:写法简单,实际上在类装载的时候就完成类实例化,避免类线程同步问题。
  • 缺点:在类装载的时候就完成了实例化,没有实现Lazy Loading懒加载的效果。如果从始至终都没有使用过这个类,会造成内存的浪费。

这种方法基于classloader机制避免了多线程的同步问题,不过,instance在类加载时就实例化,在单例模式中大多数都是调用getInsance方法,但是导致类加载的原因有多种,因此不能确定有其他的方式(或者其他的静态方法)导致类加载,这时候初始化instance就没有达到懒加载的效果。

因此:这种单例模式可以,但是可能会造成内存的浪费。除非开发人员能肯定,此类一定会被用到,那么内存就会不浪费。

2 饿汉模式(静态代码块)
package top.saodisheng.designpattern.singleton.v2;

import org.junit.jupiter.api.Test;

/**
 * description:
 * 饿汉模式(静态代码块)
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    // 1. 构造器私有化
    private Singleton() {}

    // 2. 本类内部创建对象实例
    private static Singleton instance;

    // 在静态代码块中,创建单例对象
    static {
        instance = new Singleton();
    }

    // 3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }

    @Test
    public void test1() {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        System.out.println(instance1 == instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

image-20211106100711291

优缺点:

这种方式和上一种类似,都是在类内部实例化,只不过类的实例化放在了静态代码块中。类一加载,就执行类静态代码块中的代码。优缺点和上面一样。

这种单例模式可以,但是如果对应的类没有使用可能会造成内存的浪费。

3 懒汉模式(线程不安全)
package top.saodisheng.designpattern.singleton.v3;

import org.junit.jupiter.api.Test;

/**
 * description:
 * 懒汉式(线程不安全)
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    private static Singleton instance;

    /**
     * 构造器私有化
     */
    private Singleton() {}

    /**
     * 提供一个静态的公共方法,当使用到该方法时,才创建instance,即懒汉式
     */
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }


    @Test
    public void test1() {
        // 测试
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

image-20211106101557118

优缺点:

  • 起到了懒加载的作用,但是只能在单线程下使用
  • 如果在多线程下,一个线程进入了if(instance == null) 判断语句块,可能另外一个线程也通过了这个语句判断,这时会产生多个实例,所以在多线程环境下不可使用这种方式

在实际开发中一般不使用这种方式,往往会采用加锁进行双重判断的懒汉模式

4 懒汉模式(线程安全,同步方法)
package top.saodisheng.designpattern.singleton.v4;

import org.junit.jupiter.api.Test;

/**
 * description:
 * 懒汉式(线程安全,同步方法)
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    private static Singleton instance;

    /**
     * 构造器私有化
     */
    private Singleton() {}

    /**
     * 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
     */
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    @Test
    public void test1() {
        // 测试
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

image-20211106102312151

优缺点:

  • 虽然加入了保证线程安全的关键字,解决线程安全问题(其实实际上并不一定就是线程安全:编译器(JIT),CPU有可能对指令进行重排序
  • 效率低下,每个线程都想或得实例的时候,执行getInstance方法都要进行同步。方法同步的效率较低。

综上,在实际开发中,这种方式也较少使用。

5 懒汉模式(线程安全,同步代码块)
package top.saodisheng.designpattern.singleton.v5;

import org.junit.jupiter.api.Test;

/**
 * description:
 * 懒汉式(线程安全,同步代码块)
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    private static Singleton instance;

    /**
     * 构造器私有化
     */
    private Singleton() {}

    /**
     * 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
     */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    @Test
    public void test1() {
        // 测试
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

image-20211106103208561

优缺点:

  • 虽然加锁做了一定的优化,但总体优缺点还是与同步方法的方式一致。
6 双重检查(DCL)
package top.saodisheng.designpattern.singleton.v6;

import org.junit.jupiter.api.Test;

/**
 * description:
 * 双重检查DCL
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    /**
     * 添加volatile关键字,防止指令重排
     */
    private static Singleton instance;

    /**
     * 构造器私有化
     */
    private Singleton() {}

    /**
     * 提供对外引用的静态方法,使用双重检查机制 double check
     */
    public static Singleton getInstance() {
        // Double Check Lock
        if (instance == null) {
            // 双重检查
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    @Test
    public void test1() {
        // 测试
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

image-20211106103744359

编译器(JIT), CPU可能会对指令进行重排,导致使用尚未初始化的实例,可以通过添加volatile关键字进行修饰。

优缺点:

  • Double-Check 概念是多线程中常使用到的,用于保证线程安全。
  • 实例化代码只执行一次,后面在进行访问的时候,判断if (instance == null),直接return实例化对象,也避免了反复进行方法同步。

最重要的是线程安全,延迟加载,效率高。在实际开发中,一般推荐这种单例设计模式。

7 静态内部类
package top.saodisheng.designpattern.singleton.v7;

/**
 * description:
 * 静态内部类完成单例模式,推荐使用
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Singleton {
    /**
     * 添加一个volatile关键字,防止指令重排
     */
    private static volatile Singleton instance;

    /**
     * 构造函数私有化
     */
    private Singleton() {}

    /**
     * 提供一个静态内部类,该类中有一个静态属性Singleton
     */
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    /**
     * 提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
     * @return
     */
    public static synchronized Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

image-20211106104754386

优缺点:

  • 本质上是利用类的加载机制来保证线程安全
  • 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
  • 优点就是避免了线程不安全,利用静态内部类的特点实现延迟加载,效率高

静态内部类的方法在singleton类被装载时并不会被立即实例化,而是在需要实例化的时候,调用getInstance方法,才会装载SingleInstance类,从而完成Singleton的实例化类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化的时候,别的线程是无法进入的。

这是方式在实际开发中经常使用的。

8 枚举
package top.saodisheng.designpattern.singleton.v8;

import org.junit.jupiter.api.Test;

/**
 * description:
 * 使用枚举,可以实现单例,推荐使用
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public enum Singleton {
    /** 属性 **/
    INSTANCE;
    public void hello() {
        System.out.println("hello word!! this is saodisheng");
    }
}

class testClass{
    @Test
    public void test1() {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;

        System.out.println(instance1 == instance2);

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
        instance1.hello();
    }
}

image-20211106105734939

优缺点:

  • 天然不支持反射创建对应的实例,且有自己的反序列化机制
  • 利用类加载机制保证线程安全

这借助JDK1.5中添加的枚举类来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建的对象。推荐使用。

反射攻击实例

用饿汉模式作为例子,

package top.saodisheng.designpattern.singleton.reflect;

/**
 * description:
 * 饿汉模式防止反射攻击
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class HungrySingleton {
    private static final HungrySingleton instance;

    private HungrySingleton() {}

    static {
        instance = new HungrySingleton();
    }

    public static HungrySingleton getInstance() {
        return instance;
    }

    private Object readResolve() {
        return instance;
    }
}

写一个利用反射获取的实例和直接获取实例的方法,将两者的返回值进行比较

package top.saodisheng.designpattern.singleton.reflect;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * description:
 * 通过反射获取实例
 *
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
public class Reflectattack {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);

        HungrySingleton instance1 = declaredConstructor.newInstance();
        HungrySingleton instance2 = HungrySingleton.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance1 == instance2);
    }
}

image-20211106110835869

运行后的结果为:可见,两者的结果并不是一个对象,则饿汉模式依然受到了反射的攻击。

如何防止反射?

类加载器在加载类的时候就加载了实例对象,因为在类加载的时候就生成了这个实例,所以只要我们在构造器里面加一个判断,就可以防止反射攻击:

image-20211106111257379

image-20211106111325674

这种方式有一个特点,也就是它对类加载这个时刻就把对象创建好的这种类是ok的,静态内部类的单例也可以用。对于不是静态类的也需要解决下,要根据创建实例的顺序进行解决。但是无论如何反射都可以访问到类的方法和变量,进行修改,所以非类加载这个时刻就把对象创建好的这种类,是不能防止反射攻击的。

源码中的使用

//JDK&Spring
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
//Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
// 反序列化指定数据源
java.util.Currency
......

3.2 创建型模式2——工厂方法(Factory Method)

速记关键词:动态生产对象

简单工厂

简单工厂:一个工厂根据传入的参量决定创建出哪一种产品类的实例

解决的问题:

将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需的“产品类”,从而避免了在客户端代码中显式指定,实现解耦。

即使用者可以直接消费产品而不需要知道其生产的细节。

模式组成:

组成(角色) 关系 作用
抽象产品(Product) 具体产品的父类 描述产品的公共接口
具体产品(Concrete Product) 抽象产品的子类;工厂类创建的目标类 描述生产的具体产品
工厂(Creator) 被外界调用 根据传入不同参数从而创建不同具体产品类的实例
使用步骤:
  1. 创建抽象产品类 & 定义具体产品的公共接口;
  2. 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
  3. 创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例;
  4. 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
package top.saodisheng.designpattern.simplefactory;

/**
 * description:
 * 1. 创建抽象产品类,定义具体产品的公共接口
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */
abstract class Product {
    public abstract void method1();
}


/**
 * 2 创建具体产品类
 * 具体产品类A
 */
class ProductA extends Product {
    @Override
    public void method1() {
        System.out.println("生产出了产品A");
    }
}

/**
 * 具体产品类B
 */
class ProductB extends Product {
    @Override
    public void method1() {
        System.out.println("生产出了产品C");
    }
}

/**
 * 具体产品类C
 */
class ProductC extends Product {
    @Override
    public void method1() {
        System.out.println("生产出了产品C");
    }
}


/**
 * 3. 创建工厂类,通过静态方法从而根据传入参数创建不同具体产品类的实例
 */
class Factory {
    public static Product Manufacture(String ProductName){
        //工厂类里用switch语句控制生产哪种商品;
        //使用者只需要调用工厂类的静态方法就可以实现产品类的实例化。
        switch (ProductName){
            case"A":
                return new ProductA();
            case"B":
                return new ProductB();
            case"C":
                return new ProductC();
            default:
                return null;
        }
    }
}

/**
 * 4. 工厂产品生产流程
 */
public class SimpleFactoryTest {
    public static void main(String[] args){
        Factory mFactory = new Factory();
        //客户要产品A
        try {
            //调用工厂类的静态方法 & 传入不同参数从而创建产品实例
            mFactory.Manufacture("A").method1();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }
        //客户要产品B
        try {
            mFactory.Manufacture("B").method1();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }
        //客户要产品C
        try {
            mFactory.Manufacture("C").method1();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }
        //客户要产品D
        try {
            mFactory.Manufacture("D").method1();
        }catch (NullPointerException e){
            System.out.println("没有这一类产品");
        }
    }
}

image-20211106202148015

优缺点

主要优点:

  1. 将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦。
  2. 把初始化实例的工作放在工厂里进行,使代码更容易维护。更符合面向对象的原则和面向接口编程,而不是面向实现编程。

主要缺点:

  1. 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会收到影响。
  2. 违背“开放-关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
  3. 简单工厂模型由于使用了静态代理方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
应用场景
  • 客户如果知道传入的工厂类参数,对如何创建对象的逻辑不关心时;
  • 当工厂类负责创建的对象(具体产品)比较少时。

工厂方法

工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使得一个类的实例化延迟到子类。

工厂方法模式符合“开闭原则”,当需要增加一个新的产品时,只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统。同时在工厂方法模式中用户只需要知道生产产品的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。虽然他很好的符合了“开闭原则”,但是由于每新增一个新产品时就需要增加两个类,这样势必会导致系统的复杂度增加。

img

解决的问题

工厂一旦需要生产新产品就需要修改工厂逻辑,违背了“开放-关闭原则”

  1. 即简单工厂方法的缺点
  2. 之所以可以解决简单工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中。此时工厂类不在负责所有产品的创建,而知识给出具体工厂必须实现的接口,这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开闭原则,克服了简单工厂模式中的缺点。
模式组成
组成(角色) 关系 作用
抽象产品(Product) 具体产品的父类 描述具体产品的公共接口
具体产品(Concrete Product) 抽象产品的子类;工厂类创建的目标类 描述生产的具体产品
抽象工厂(Creator) 具体工厂的父类 描述具体工厂的公共接口
具体工厂(Concrete Creator) 抽象工厂的子类;被外界调用 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例
使用步骤
  1. 创建抽象工厂类,定义具体工厂的公共接口;
  2. 创建抽象产品类 ,定义具体产品的公共接口;
  3. 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
  4. 创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
  5. 外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例。
package top.saodisheng.designpattern.factorypattern.factorymethod;

/**
 * 1. 创建一个抽象工厂
 */
abstract class Factory{
    public abstract Product Manufacture();
}

/**
 * 2. 创建一个接口
 */
interface Product{
    public void method();
}

/**
 * 3. 创建接口的实现类
 */
class ProductA implements Product{
    @Override
    public void method() {
        System.out.println("I'm ProductA!");
    }
}

/**
 * 3. 创建接口的实现类
 */
class ProductB implements Product{
    @Override
    public void method() {
        System.out.println("I'm ProductA!");
    }
}

/**
 * 4 具体工厂类,工厂A类 - 生产A类产品
 */
class FactoryA extends Factory {
    @Override
    public Product Manufacture() {
        return new ProductA();
    }
}

/**
 * 5 具体工厂类,工厂B类 - 生产B类产品
 */

class FactoryB extends Factory {
    @Override
    public Product Manufacture() {
        return new ProductB();
    }
}

/**
 * description:
 * 5 外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例
 * @author 扫地生_saodisheng
 * @date 2021/11/6
 */
public class FactoryMethodTest {
    public static void main(String[] args) {
        //客户要产品A
        FactoryA mFactoryA = new FactoryA();
        mFactoryA.Manufacture().method();

        //客户要产品B
        FactoryB mFactoryB = new FactoryB();
        mFactoryB.Manufacture().method();
    }
}

image-20211106204016939

优缺点

优点:

  • 用户只需要知道具体工厂的名称就可得到所有的产品,无需知道产品的具体创建过程,
  • 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
  • 典型的解耦框架。高层模块只需要知道产品的抽象类,无需关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。

缺点:

  • 类的个数容易过多,增加复杂度
  • 增加了系统的抽象性和理解难度
  • 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决
应用场景:
  1. 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
  2. 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  3. 客户不关心创建产品的细节,只关心产品的品牌。

源码中的应用

//java api
// 静态工厂方法
Calendar.getInstance()
java.text.NumberFormat.getInstance()
java.util.ResourceBundle.getBundle()

// 工厂方法
java.net.URLStreamHandlerFactory
javax.xml.bind.JAXBContext.createMarshaller
......

3.3 创建型模式3——抽象工厂模式(Abstract Factory)

速记关键词:生产系列对象

简介

定义:提供一个创建一系列相关或互相依赖的对象接口,而无需指定他们具体的类。

他允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更加需要更改接口及其下所有子类。

img

解决的问题

每个方法只能创建一类产品。

即工厂方法模式的缺点

模式组成

抽象工厂同工厂模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。

组成(角色) 关系 作用
抽象产品族(AbstractProduct) 抽象产品的父类 描述抽象产品的公共接口
抽象产品(Product) 具体产品的父类 描述具体产品的公共接口
具体产品(Concrete Product) 抽象产品的子类;工厂类创建的目标类 描述生产的具体产品
抽象工厂(Creator) 具体工厂的父类 描述具体工厂的公共接口
具体工厂(Concrete Creator) 抽象工厂的子类;被外界调用 描述具体工厂;实现FactoryMethod工厂方法创建产品的实例

使用步骤

  1. 创建抽象工厂类,定义具体工厂的公共接口;
  2. 创建抽象产品族类 ,定义抽象产品的公共接口;
  3. 创建抽象产品类 (继承抽象产品族类),定义具体产品的公共接口;
  4. 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
  5. 创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
  6. 客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例
package top.saodisheng.designpattern.factorypattern.abstractfactory;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */

/**
 * 1.创建产品类接口
 * 产品A接口
 */
interface ProductA{
    void methodA();
}

/**
 * 产品B接口
 */
interface ProductB{
    void methodB();
}

/**
 * 2.创建产品类的实现类
 */
class ProductA1 implements ProductA {
    @Override
    public void methodA() {
        System.out.println("I'm ProductA1");
    }
}
class ProductA2 implements ProductA {
    @Override
    public void methodA() {
        System.out.println("I'm ProductA2");
    }
}
class ProductB1 implements ProductB {
    @Override
    public void methodB() {
        System.out.println("I'm ProductB1");
    }
}
class ProductB2 implements ProductB {
    @Override
    public void methodB() {
        System.out.println("I'm ProductB2");
    }
}

/**
 * 3.创建工厂的接口
 */
interface Creator {
    /**
     * 专门生产A类产品
     * @return
     */
    ProductA createProductA();

    /**
     * 专门生产B类产品
     * @return
     */
    ProductB createProductB();
}

/**
 * 4.创建工厂的实现类
 * 生产1型产品
 */
class ConcreteCreator1 implements Creator {
    @Override
    public ProductA createProductA() {
        return  new ProductA1();
    }
    @Override
    public ProductB createProductB() {
        return  new ProductB1();
    }
}

/**
 * 生产2型产品
 */
class ConcreteCreator2 implements Creator {
    @Override
    public ProductA createProductA() {
        return  new ProductA2();
    }
    @Override
    public ProductB createProductB() {
        return  new ProductB2();
    }
}
public class AbstructFactoryMethod {
    public static void main(String[] args) {
        // 生产1型产品
        Creator concreteCreator1 = new ConcreteCreator1();
        ProductA productA = concreteCreator1.createProductA();
        ProductB productB = concreteCreator1.createProductB();
        productA.methodA();
        productB.methodB();

        // 成产2型产品
        Creator concreteCreator2 = new ConcreteCreator2();
        productA = concreteCreator2.createProductA();
        productB = concreteCreator2.createProductB();
        productA.methodA();
        productB.methodB();
    }
}

image-20211106210534801

优缺点

优点:

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 但需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
  • 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。

缺点:

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

对于新的产品族符合开-闭原则;对于新的产品种类不符合开-闭原则,这一特性称为开-闭原则的倾斜性。

应用场景

程序需要处理不同系列的相关产品,但是不希望他依赖于这些产品的具体类是,可以使用出抽象工厂。

源码中的应用

#JDK
java.sql.Connection
java.sql.Driver

# mybatis
SqlSessionFactory

3.4 创建型模式4——建造者模式(Builder)

速记关键词:复杂对象构件

简介

定义:将一个复杂对象的创建与他的表示分离,使得同样的构件过程可以创建不同的表示。

建造者模式将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。但是如果某个产品的内部结构过于复杂,将会导致整个系统变得非常庞大,不利于控制,同时若几个产品之间存在较大的差异,则不适用建造者模式。

img

解决的问题

  1. 降低创建复杂对象的复杂对
  2. 隔离了创建对象的构建过程和表示

从而:

  • 方便用户创建复杂对象(不需要知道实现过程)
  • 代码复用性&封装性(将对象构建和细节进行封装&复用)

模式组成

组件 说明
指挥者 Director 1. 隔离了客户与对象的生产过程;2. 负责控制产品对象的生产过程。
建造者 Builder 声明了具体建造者的公共接口,隔离了产品的建造过程&返回产品的方法
具体建造者 ConcreteBuilder 实现Builder接口来创建具体产品
具体产品 Product 即建造后的对象

使用步骤

  1. 最终构建成具体产品(Product)。
  2. 将客户创建产品的需求划分为各个部件的建造请求(Builder);
  3. 将各个部件的建造请求委派到具体的建造者(ConcreteBuilder);
  4. 指挥者(Director)直接和客户(Client)进行需求沟通;
  5. 各个具体建造者负责进行产品部件的构建;
package top.saodisheng.designpattern.builder;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-04
 */

/**
 * 1.创建指定类(产品类)
 */
class Product{
    /**
     * 产品组件1
     */
    private String part1;
    /**
     * 产品组件2
     */
    private String part2;

    public String getPart1() {
        return part1;
    }

    public void setPart1(String part1) {
        this.part1 = part1;
    }

    public String getPart2() {
        return part2;
    }

    public void setPart2(String part2) {
        this.part2 = part2;
    }
}

/**
 * 2.创建建造者接口
 */
interface Builder{
    public void buildPart1();
    public void buildPart2();
    public Product retriveResult();
}

/**
 * 3.创建具体的建造者实现接口
 */
class ConcreteBuilder implements Builder {
    private Product product = new Product();

    /**
     * 生成产品组件1
     */
    @Override
    public void buildPart1() {
        product.setPart1("产品编号:0000");
    }

    /**
     * 生成产品组件2
     */
    @Override
    public void buildPart2() {
        product.setPart2("产品名称:XXX");

    }

    /**
     * 产品返回方法
     * @return
     */
    @Override
    public Product retriveResult() {
        return product;
    }
}

/**
 * 4. 创建生产产品的总管
 */
class Director {
    // 拥有建造者
    private Builder builder;

    /**
     * 构造方法,传入建造者对象,类比现实中老板招募技术总监
     * @param builder 建造者对象
     */
    public Director(Builder builder) {
        this.builder = builder;
    }

    /**
     * 产品构造方法,负责调用各个零件建造方法
     */
    public void construct() {
        builder.buildPart1();
        builder.buildPart2();
    }
}

/**
 * 5. 客户使用
 */
public class BuilderClient {
    public static void main(String[] args) {
        // 建造者
        Builder builder = new ConcreteBuilder();
        // 总管招募建造者
        Director director = new Director(builder);
        // 总管生成产品
        director.construct();
        // 建造者提交结果
        Product product = builder.retriveResult();
        System.out.println(product.getPart1());
        System.out.println(product.getPart2());
    }
}

image-20211106213655935

优缺点

优点:

  • 良好的封装性:建造者对客户端屏蔽了产品内部组成的细节,客户端不用关心每一个具体的产品内部是如何实现的。
  • 符合开闭原则
  • 便于控制细节风险:由于建造者是相互独立的,因此可以对建造者过程组件细化,而不对其他的模块产生任何影响。

每个具体建造者都相互独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。

缺点:

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

应用场景

  1. 需要生成的对象具有复杂的内部结构
  2. 需要生成的对象内部属性本身相互依赖
  3. 与不可变对象配合使用

与工厂方法模式的区别

建造者模式最主要的功能是基于方法的调用顺序安排,基本方法已经实现,我们可以理解为零件的装配,顺序不同产生的对象也不同;而工厂方法的重点是创建,创建零件是其主要职责,不关心组装的顺序。

源码中的应用

# jdk
java.lang.StringBuilder
# Spring源码
org.springframework.web.servlet.mvc.method.RequestMappingInfo
org.springframework.beans.factory.support.BeanDefinitionBuilder
......

3.5 创建型模式5——原型模式(prototype)

速记关键词:克隆对象

简介

定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

img

浅拷贝:按位拷贝,它会创建一个对象,这个对象拥有原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性时内存地址(引用类型),拷贝的就是内存地址,因此如果其中一个对象改变了这个地址,就会影响另一个对象(即复制了对象的引用地址,两个对象同时指向同一个地址,所以修改其中任意的值,另一个值也会发生改变)。

深拷贝:会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和他所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度慢并且开销大。

浅克隆

package top.saodisheng.designpattern.prototype;

/**
 浅拷贝
 * @author 扫地生_saodisheng
 * @date 2021-02-04 16:21
 */

/**
 * 用户信息,包括了基本数据类型和引用数据类型
 */
class User implements Cloneable{
    // 基础数据类型
    private int id;
    private String name;
    private String sex;
    private String pwd;

    // 引用数据类型
    private BaseInfo baseInfo;

    public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.pwd = pwd;
        this.baseInfo = baseInfo;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public BaseInfo getBaseInfo() {
        return baseInfo;
    }

    public void setBaseInfo(BaseInfo baseInfo) {
        this.baseInfo = baseInfo;
    }

    @Override
    public String toString() {
        return "hashCode: " + super.hashCode() + ", User{ " + "id = " + id + ", name = " + name
                + ", sex = " + sex + ", pwd = " + pwd + ", baseInfo" + baseInfo + " }";
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

class BaseInfo {
    private String desc;

    public BaseInfo(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "BaseInfo{ " + "desc = " + desc + "}";
    }
}
public class PrototypeShallowCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        BaseInfo baseInfo = new BaseInfo("张三");
        User user = new User(1, "张三", "男", "123456", baseInfo);
        // 克隆
        User user2 = user.clone();
        System.out.println("浅克隆");
        System.out.println(user);
        System.out.println(user2);

        user2.setName("李四");
        System.out.println("修改基本数据类型");
        System.out.println(user);
        System.out.println(user2);

        user2.getBaseInfo().setDesc("李四");
        System.out.println("修改引用数据类型");
        System.out.println(user);
        System.out.println(user2);
    }
}

image-20211106234155483

深克隆

深克隆,即在浅克隆引用类型成员变量时,为引用类型的数据成员另辟了一个独立的空降,实现真正内容上的克隆。

// 深克隆代码和浅克隆基本一致,不同之处在对引用数据类型添加单独的克隆方法。

public class BaseInfo implements Cloneable{
    private String desc;

    public BaseInfo(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "BaseInfo{ " + "desc = " + desc + "}";
    }

    @Override
    public BaseInfo clone() throws CloneNotSupportedException {
        return (BaseInfo) super.clone();
    }
}

User类中修改的代码
protected User clone() throws CloneNotSupportedException {
    User user = (User)super.clone();        
    user.baseInfo = baseInfo.clone();
    return user;
}

image-20211106234047883

由输出结果可见,深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响

序列化机制实现深克隆

需要在 User 类实现 Serializable,成员类型(BaseInfo)也需要实现 Serializable 接口。

package top.saodisheng.designpattern.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 用户信息
 */
public class User implements Cloneable , Serializable {

    // 基础数据类型
    private int id;
    private String name;
    private String sex;
    private String pwd;

    // 引用数据类型
    private BaseInfo baseInfo;

    public User(int id, String name, String sex, String pwd, BaseInfo baseInfo) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.pwd = pwd;
        this.baseInfo = baseInfo;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public BaseInfo getBaseInfo() {
        return baseInfo;
    }

    public void setBaseInfo(BaseInfo baseInfo) {
        this.baseInfo = baseInfo;
    }

    @Override
    public String toString() {
        return "hashCode: " + super.hashCode() + ", User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", pwd='" + pwd + '\'' +
                ", baseInfo=" + baseInfo +
                '}';
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        // 深拷贝
        // User user = (User) super.clone();
        // user.baseInfo = baseInfo.clone();
        // return user;


        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        try (ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream)) {

            oos.writeObject(this);

        } catch (IOException e) {
            e.printStackTrace();
        }

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

        try (ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream)) {
            try {
                User user = (User) ois.readObject();
                return user;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

优缺点

优点:

  • 可以不耦合具体类的情况下克隆对象
  • 避免重复的初始化代码
  • 更方便的构建复杂对象

缺点:

  • 适用性不是很广。
  • 每一个类必须配备一个克隆方法。
  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

应用场景

当代吗不应该依赖与需要复制的对象的具体类时,使用Prototype模式。

  • 某些结构复杂的对象的创建公祖;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定的接口。
  • 一般在初始化的信息不发生变化的情况下,克隆是最好的选择。

源码中的应用

#Spring 
org.springframework.beans.factory.support.AbstractBeanDefinition
#JDK
java.util.Arrays
java.util.ArrayList
......
posted @ 2021-11-07 11:32  技术扫地生—楼上老刘  阅读(9)  评论(0编辑  收藏  举报