当前位置:   article > 正文

1、创建者模式(5种)

创建者模式

1、创建者模式

创建者模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 原型模式
  • 建造者模式

2、单例设计模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

2.1、单例模式的结构

单例模式的主要有以下角色:

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类

2.2、单例模式的实现

单例设计模式分类有两种:

  • 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
2.2.1、饿汉式-方式1(静态成员遍历方式)
package org.singleton;

/**
 * 饿汉式
 *      静态变量创建类的对象
 */
public class Singleton {

    // 在成员位置创建该类对象
    private static Singleton instance = new Singleton();

    // 私有构造方法
    private Singleton() {
    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

说明:

​ 该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance 对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2.2.2、饿汉式-方式2(静态代码块方式)
package pattern.singleton.demo2;

/**
 * 饿汉式
 *      在静态代码块中创建该类对象
 */
public class Singleton {

    // 私有构造方法:为了让外界不能创建该类对象
    private Singleton() {}

    // 声明Singleton类型的变量
    private static Singleton instance; // null

    // 在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }

    // 对外提供获取该类对象的方法
    public static Singleton getInstance() {
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

说明:

​ 该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方法也存在内存浪费问题。

2.2.3、懒汉式-方式1(线程不安全)
package pattern.singleton.demo3;

/**
 * 懒汉式
 *      线程不安全
 */
public class Singleton {

    // 私有化构造方法
    private Singleton() {}

    // 在成员位置声明Singleton类型的变量instance
    private static Singleton instance; // 只是声明了一个该类型的变量,并没有为其赋值

    // 对外提供访问方式
    public static Singleton getInstance() {
        // 判断instance是否为null:如果为null,说明还没有创建Singleton类的对象
        // 如果没有,创建一个并返回;如果有,直接返回
        if (instance == null) {
            // 线程1等待,线程2获取到cpu的时间片,也会进入到该判断里面
            instance = new Singleton();
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

说明:

​ 从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用 getInstance() 方法获取 Singleton 类的对象的时候才创建 Singleton 类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,就会出现线程安全问题。

2.2.4、懒汉式-方式2(线程安全)
package pattern.singleton.demo4;

/**
 * 懒汉式
 *      线程安全
 */
public class Singleton {

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

    // 声明一个Singleton类型的成员变量
    private volatile static Singleton instance;

    // 对外提供静态方法获取该对象
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

说明:

​ 该方式也实现了懒加载效果,同时又解决了线程安全问题,但是在 getInstance() 方法上添加了 synchronized 关键字,导致该方法的执行效率特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。

2.2.5、懒汉式-方式3(双重检测锁)

再来讨论以下懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,该操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也就产生了一种新的实现模式:双重检查锁

package pattern.singleton.demo4;

/**
 * 懒汉式
 *      双重检查锁方式
 */
public class Singleton {

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

    // 声明一个Singleton类型的成员变量
    private static Singleton instance;

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        // 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题的,在多线程的情况下,可能会出现空指针问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作。要解决双重检测锁模式带来的空指针异常的问题,只需要使用 volatile 关键字,volatile 关键字可以保证可见性和有序性。

package pattern.singleton.demo4;

/**
 * 懒汉式
 *      双重检查锁方式
 */
public class Singleton {

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

    // 声明一个Singleton类型的成员变量
    private volatile static Singleton instance;

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        // 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

小结:

添加 volatile 关键字之后的双重检测锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会又性能问题。

2.2.6、懒汉式-方式4(静态内部类方式)

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性,静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序(解决了指令重排序问题)。

package pattern.singleton.demo5;

/**
 * 静态内部类方式
 */
public class Singleton {

    // 私有构造方法
    private Singleton() {

    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 定义一个静态内部类
    private static class SingletonHolder {
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

说明:

​ 第一次加载 Singleton 类时不会去初始化 INSTANCE,只有第一次调用 getInstance,虚拟机加载 SingletonHolder 并初始化 INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

小结:

​ 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有任何加锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

2.2.7、枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会加载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一不会被破坏的单实例实现模式。

package pattern.singleton.demo6;

/**
 * 枚举方式
 */
public enum Singleton {
    INSTANCE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

说明:

​ 枚举方式属于饿汉式方式,不考虑浪费内存空间的情况下可以首选枚举方式实现单例模式。

2.3、存在的问题

2.3.1、问题演示

破坏单例模式:

使上面定义的单例类(Singleton类)可以创建多个对象,枚举方式除外(枚举是JVM底层实现,已经把所有问题都考虑到了)。有两种方式,分别是序列化和反射。

  • 序列化反序列化

    Singleton类:要实现序列化接口否则无法通过对象流进行转化

    package pattern.singleton.demo7;
    
    import java.io.Serializable;
    
    /**
     * 懒汉式
     *      线程不安全
     */
    public class Singleton implements Serializable {
    
        // 私有化构造方法
        private Singleton() {}
    
        // 在成员位置声明Singleton类型的变量instance
        private static Singleton instance; // 只是声明了一个该类型的变量,并没有为其赋值
    
        // 对外提供访问方式
        public static synchronized Singleton getInstance() {
            // 判断instance是否为null:如果为null,说明还没有创建Singleton类的对象
            // 如果没有,创建一个并返回;如果有,直接返回
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    Client类:

    package pattern.singleton.demo7;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class Client {
    
        public static void main(String[] args) throws Exception {
            // writeObjectFile();
            /**
             * pattern.singleton.demo7.Singleton@6d03e736
             * pattern.singleton.demo7.Singleton@568db2f2
             */
            readObjectFromFile();
            readObjectFromFile();
        }
    
        // 从文件中读取数据(对象)
        public static void readObjectFromFile() throws Exception {
            // 1、创建对象输入流对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\\\Users\\\\qibie\\\\Desktop\\\\design_pattern\\\\code\\\\principles\\\\principles\\\\src\\\\main\\\\java\\\\a.txt"));
            // 2、读取对象
            Singleton instance = (Singleton) ois.readObject();
            System.out.println(instance);
            // 3、释放资源
            ois.close();
        }
    
        // 向文件中写数据(对象)
        public static void writeObjectFile() throws Exception {
            // 1、获取Singleton对象
            Singleton instance = Singleton.getInstance();
            // 2、创建对象输出流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\qibie\\Desktop\\design_pattern\\code\\principles\\principles\\src\\main\\java\\a.txt"));
            // 3、写对象
            oos.writeObject(instance);
            // 4、释放资源
            oos.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    上面运行代码输出的两个对象地址不同,表名序列化已经破坏了单例设计模式。

  • 反射

    Singleton类:

    package pattern.singleton.demo8;
    
    /**
     * 静态内部类方式
     */
    public class Singleton {
    
        // 私有构造方法
        private Singleton() {
    
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Client类

    package pattern.singleton.demo8;
    
    import java.lang.reflect.Constructor;
    
    /**
     * 测试反射破坏单例模式的客户端类
     */
    public class Client {
    
        public static void main(String[] args) throws Exception {
            // 1、获取Singleton的字节码对象
            Class clazz = Singleton.class;
            // 2、获取无参构造方法对象
            Constructor constructor = clazz.getDeclaredConstructor(); // 获取私有无参构造器
            // 3、取消访问检查
            constructor.setAccessible(true);
            // 4、创建Singleton对象
            Singleton s1 = (Singleton) constructor.newInstance();
            Singleton s2 = (Singleton) constructor.newInstance();
            System.out.println(s1 == s2); // 如果返回的是true说明并没有破坏单例模式,如果是false说明破坏了单例模式
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
2.3.2、问题的解决
  • 序列化、反序列化破坏单例模式的解决方法

    在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

    package pattern.singleton.demo9;
    
    import java.io.Serializable;
    
    public class Singleton implements Serializable {
    
        // 私有构造方法
        private Singleton() {}
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        /**
         * 下面是为了解决序列化反序列化破坏单例模式
         * 当进行反序列化时会自动调用该方法,将该方法的返回值直接返回
         */
        public Object readResolve() {
            return SingletonHolder.INSTANCE;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 反射方式破解单例的解决方法

    package pattern.singleton.demo8;
    
    import java.io.Serializable;
    
    public class Singleton implements Serializable {
    
        private static boolean flag = false;
    
        // 私有构造方法
        private Singleton() {
            synchronized (Singleton.class) {
                // 判断 flag 的值是否是 true,如果是 true,说明非第一次访问,直接抛一个异常即可;如果是false,说明第一次访问
                if (flag) {
                    throw new RuntimeException("不能创建多个对象");
                }
                // 将 flag 的值设置为true
                flag = true;
            }
        }
    
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

2.4、JDK源码解析——Runtime类

Runtime 类就是使用的单例设计模式

  1. 通过源代码查看使用的是哪几种单例模式

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {}
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    从上面源代码可以看出Runtime类使用的是饿汉式(静态属性)方式来实现单例模式的。

  2. 使用Runtime类中的方法

    package pattern.singleton.demo10;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class RuntimeDemo {
    
        public static void main(String[] args) throws IOException {
    
            // 获取Runtime类的对象
            Runtime runtime = Runtime.getRuntime();
    
            // 调用 Runtime 对象的方法 exec,参数要的是一个命令
            Process process = runtime.exec("ipconfig");
            // 调用 process 对象的获取输入流的方法
            InputStream is = process.getInputStream();
            byte[] arr = new byte[1024 * 1024 * 100];
            // 读取数据
            int len = is.read(arr); // 返回读到的字节的个数
            // 将字节数组转换成字符串输出到控制台
            System.out.println(new String(arr, 0, len - 1, "GBK"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

3、工厂模式

3.1、概述

需求:设计一个咖啡店点餐系统。

设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店(CoffeeStore),咖啡店具有点咖啡的功能。

具体类的设计如下:

在这里插入图片描述

代码实现如下:

package factory.before;

public abstract class Coffee {

    public abstract String getName();

    // 加糖
    public void addSugar() {
        System.out.println("加糖");
    }

    // 加奶
    public void addMilk() {
        System.out.println("加奶");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
package factory.before;

public class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
package factory.before;

public class LatteCoffee extends Coffee {
    
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
package factory.before;

public enum EnumCoffeeType {

    American,
    Latte;

    EnumCoffeeType() {
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
package factory.before;

public class CoffeeStore {

    public Coffee orderCoffee(EnumCoffeeType type) {
        // 声明Coffee类型的变量
        Coffee coffee = null;
        // 根据不同类型创建不同的coffee子类对象
        switch (type) {
            case Latte:
                coffee = new LatteCoffee();
                break;
            case American:
                coffee = new AmericanCoffee();
                break;
        }
        if (coffee == null) {
            throw new RuntimeException("对不起,您所点的咖啡没有!");
        }
        // 添加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
package factory.before;

public class Client {

    public static void main(String[] args) {

        // 1、创建咖啡点类
        CoffeeStore store = new CoffeeStore();
        // 2、点咖啡
        Coffee coffee = store.orderCoffee(EnumCoffeeType.Latte);

        System.out.println(coffee.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在Java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。

本文将介绍三种工厂的使用:

  • 简单工厂模式(不属于GOF的23种经典设计模式)
  • 工厂方法模式
  • 抽象工厂模式

3.2、简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯。

3.2.1、结构

简单工厂包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。(Coffee)
  • 具体产品:实现或继承抽象产品的子类。(AmericanCoffee、LatteCoffee etc.)
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来创建/获取产品
3.2.2、实现

现在使用简单工厂对上面案例进行改进,类图如下:

在这里插入图片描述

工厂类代码如下:咖啡类和具体咖啡类没有任何改动

package factory.simple_factory;

/**
 * 简单咖啡工厂,用来生产咖啡
 */
public class SimpleCoffeeFactory {

    public Coffee createCoffee(EnumCoffeeType type) {
        switch (type) {
            case American:
                return new AmericanCoffee();
            case Latte:
                return new LatteCoffee();
        }
        throw new RuntimeException("对不起,您所点的咖啡没有");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
package factory.simple_factory;

/**
 * 此时咖啡店不再依赖于具体的咖啡对象
 * 解除了咖啡店和具体的咖啡对象实现了解耦合
 */
public class CoffeeStore {

    public Coffee orderCoffee(EnumCoffeeType type) {
        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        // 调用生产咖啡的方法
        Coffee coffee = factory.createCoffee(type);

        // 加配料:二次加工
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
package factory.simple_factory;

public class Client {

    public static void main(String[] args) {

        // 创建咖啡店对象
        CoffeeStore store = new CoffeeStore();
        Coffee coffee = store.orderCoffee(EnumCoffeeType.American);

        System.out.println(coffee.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee() 就变成了此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了CoffeeStore和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

3.2.3、优缺点

优点:

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更容易扩展。

缺点:

增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

3.2.4、扩展

静态工厂

在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。代码如下:

package factory.static_factory;

public class StaticCoffeeFactory {

    public static Coffee createCoffee(EnumCoffeeType type) {
        switch (type) {
            case American:
                return new AmericanCoffee();
            case Latte:
                return new LatteCoffee();
        }
        throw new RuntimeException("您好,您点的咖啡没有!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
package factory.static_factory;

/**
 * 此时咖啡店不再依赖于具体的咖啡对象
 * 解除了咖啡店和具体的咖啡对象实现了解耦合
 */
public class CoffeeStore {

    public Coffee orderCoffee(EnumCoffeeType type) {
        // 调用生产咖啡的方法
        Coffee coffee = StaticCoffeeFactory.createCoffee(type);

        // 加配料:二次加工
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.3、工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

3.3.1、概念

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

3.3.2、结构
  • 抽象工厂(AbstractFactory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcretaProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
3.3.3、实现

使用工厂方法模式对上例进行改进,类图如下:

在这里插入图片描述

代码如下:

抽象工厂:

package factory.factory_method;

public interface CoffeeFactory {

    Coffee createCoffee();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

美式咖啡工厂类:

package factory.factory_method;

public class AmericanCoffeeFactory implements CoffeeFactory {
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

拿铁工厂类:

package factory.factory_method;

public class LatteCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

咖啡店:

package factory.factory_method;

public class CoffeeStore {

    // 依赖于抽象,依赖倒置原则
    private CoffeeFactory factory;

    public CoffeeStore(CoffeeFactory factory) {
        this.factory = factory;
    }

    public Coffee orderCoffee() {
        Coffee coffee = factory.createCoffee();

        // 加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

客户端:

package factory.factory_method;

public class Client {

    public static void main(String[] args) {

        CoffeeStore store = new CoffeeStore(new AmericanCoffeeFactory());
        Coffee coffee = store.orderCoffee();
        System.out.println(coffee.getName());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以上编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

3.3.4、优缺点

优点:

  • 用户只需要知道具体工厂的名称就可得到所有的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个对应的具体工厂类,这增加了系统的复杂度。

3.4、抽象工厂模式

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。

这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是现实生活中许多工厂是综合型工厂,能生产多等级(种类)的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。

在这里插入图片描述

在这里插入图片描述

3.4.1、概念

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无需指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生成一个等级的产品,而抽象工厂模式可生产多个等级的产品。

3.4.2、结构

抽象工厂模式的主要角色如下:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品(同一产品族)。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是一对一的关系。
3.4.3、实现

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂模式,需要创建提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸清空。其中拿铁咖啡、美式咖啡是一个产品等级都是咖啡,提拉米苏、抹茶慕斯又是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是统一产品族(也就是都属于美式风味),所以这个案例可以使用抽象工厂模式实现,类图如下:

在这里插入图片描述

抽象工厂类

package pattern.factory.abstract_factory;

public interface DessertFactory {

    Coffee createCoffee();

    Dessert createDessert();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

具体工厂类

package pattern.factory.abstract_factory;

public class AmericanDessertFactory implements DessertFactory {
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }

    @Override
    public Dessert createDessert() {
        return new MatchaMousse();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
package pattern.factory.abstract_factory;

public class ItalyDessertFactory implements DessertFactory {
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }

    @Override
    public Dessert createDessert() {
        return new MatchaMousse();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

抽象产品类:

package pattern.factory.abstract_factory;

public abstract class Coffee {

    public abstract String getName();

    public void addMilk() {
        System.out.println("【" + getName() + "】加奶!");
    }

    public void addSugar() {
        System.out.println("【" + getName() + "】加糖!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
package pattern.factory.abstract_factory;

public abstract class Dessert {

    public abstract void show();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

具体产品类:

package pattern.factory.abstract_factory;

public class LatteCoffee extends Coffee {
    @Override
    public String getName() {
        return "拿铁";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
package pattern.factory.abstract_factory;

public class AmericanCoffee extends Coffee {
    @Override
    public String getName() {
        return "美式";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
package pattern.factory.abstract_factory;

public class Tiramisu extends Dessert{
    @Override
    public void show() {
        System.out.println("提拉米苏");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
package pattern.factory.abstract_factory;

public class MatchaMousse extends Dessert {

    @Override
    public void show() {
        System.out.println("抹茶慕斯");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

DessertStore类:

package pattern.factory.abstract_factory;

public class DessertStore {

    private DessertFactory factory;

    public void setFactory(DessertFactory factory) {
        this.factory = factory;
    }

    public Coffee orderCoffee() {
        Coffee coffee = factory.createCoffee();

        // 加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }

    public Dessert orderDessert() {
        return factory.createDessert();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Client客户端类:

package pattern.factory.abstract_factory;

public class Client {

    public static void main(String[] args) {

        DessertStore store = new DessertStore();
        // AmericanDessertFactory factory = new AmericanDessertFactory();
        ItalyDessertFactory factory = new ItalyDessertFactory();
        store.setFactory(factory);

        Coffee coffee = store.orderCoffee();
        Dessert dessert = store.orderDessert();

        System.out.println(coffee.getName());
        dessert.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其它的类

3.4.4、优缺点

优点

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

3.4.5、使用场景
  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖于产品实例的创建细节和内部结构。

如:输入法换皮肤,一整套一起换(每次只使用某一族产品)。生成不同操作系统的程序。

3.4.6、模式扩展(开发中最常用的方式)
简单工厂+配置文件解除耦合(完全解除耦合)

Spring底层就是该原理。

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

第一步:定义配置文件

为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties

american=org.pattern.factory.config_factory.AmericanCoffee
latte=org.pattern.factory.config_factory.LatteCoffee
  • 1
  • 2
第二步:改进工厂类——静态工厂
package org.pattern.factory.config_factory;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class CoffeeFactory {

   
    // 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
    // 1、定义容器对象存储咖啡对象
    private static final Map<String, Coffee> map = new HashMap<>();
    
    static {
        Properties p = new Properties();
        // 放在类路径下所以使用类加载器去拿
        InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            p.load(is);
            // 遍历Properties集合对象
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
                // 根据键获取值(全类名)
                String classPath = p.getProperty((String) key);
                // 获取字节码对象
                Class<?> clazz = Class.forName(classPath);
                Coffee coffee = (Coffee) clazz.newInstance();
                map.put((String) key, coffee);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Coffee createCoffee(String name){
        return map.get(name);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

静态成员遍历用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。

3.4.7、JDK源码解析——Collection.iterator方法
package org.pattern.factory;

import java.util.ArrayList;
import java.util.Iterator;

// JDK 源码解析——Collection.iterator方法
public class Demo {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("令狐冲");
        list.add("风清扬");
        list.add("任我行");

        // 获取迭代器对象
        Iterator<String> iterator = list.iterator();
        // 使用迭代器遍历
        while (iterator.hasNext()) {
            String ele = iterator.next();
            System.out.println(ele);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

上面代码就是典型的使用迭代器遍历集合,获取集合中的元素,而单列集合获取迭代器的方法就使用到了工厂方法模式。我们可以通过类图看看结构:

在这里插入图片描述

Collection 接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Itr内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。

Collection接口:抽象工厂类

public interface Collection<E> extends Iterable<E> {
    ...
    // 生产抽象产品类对象的方法
    Iterator<E> iterator();
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Iterator接口:抽象产品类

public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

ArrayList类:具体工厂类 + Itr 具体产品类

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
     public Iterator<E> iterator() {
        return new Itr();
    }
    
    // 具体产品类,ArrayList中其实有很多内部类——具体产品类
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i < size) {
                final Object[] es = elementData;
                if (i >= es.length)
                    throw new ConcurrentModificationException();
                for (; i < size && modCount == expectedModCount; i++)
                    action.accept(elementAt(es, i));
                // update once at end to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

    /**
     * An optimized version of AbstractList.ListItr
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

另:

1、DateFormat类中的getInstance() 方法使用的是工厂模式;

2、Calendar类中的getInstance()方法使用的是工厂模式;

4、原型模式

4.3.1、概述

用一个已经创建的实例作为原型,通过复制该原型来创建一个和原型对象相同的新对象

4.3.2、结构

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的 clone() 方法。
  • 具体原型类:实现了抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

接口类图如下:

在这里插入图片描述

4.3.3、实现

原型模式的克隆分为浅克隆和深克隆

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java 中的 Object 类中提供了 clone() 方法来实现浅拷贝。Cloneable 接口时上面的类图中的抽象原型类,而实现了 Cloneable 接口的子实现类就是具体的原型类。代码如下:

Realizetype(具体的原型类):

package org.pattern.created.prototype;

public class Realizetype implements Cloneable {

    public Realizetype() {
        System.out.println("具体的原型对象创建完成!");
    }

    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功!");
        return (Realizetype) super.clone();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

RealizetypeTest(测试类/访问类)

package org.pattern.created.prototype;

public class RealizetypeTest {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 创建一个原型类对象
        Realizetype r1 = new Realizetype();
        
        // 调用 Realizetype 类中的 clone 方法进行对象的克隆
        Realizetype r2 = r1.clone();

        System.out.println("对象r1和r2是同一个对象? " + (r1 == r2));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

结果:

具体的原型对象创建完成!
具体原型复制成功!
对象r1和r2是同一个对象? false
  • 1
  • 2
  • 3

4.3.4 案例

用原型模式生成”三好学生“奖状

同一学校的”三好学生“奖状除了获奖人姓名不同,其它都相同,可以使用原型模式复制多个”三好学生“奖状出来,然后在修改奖状上的名字即可。

类图如下:

在这里插入图片描述

// 奖状类
@Data
public class Citation implements Cloneable {

    // 三好学生的姓名
    private String name;

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

    public void show() {
        System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }
}


public class CitationTest {

    public static void main(String[] args) throws CloneNotSupportedException {

        // 1、创建原型对象
        Citation c1 = new Citation();
        // 2、复制奖状,先克隆再给奖状写上名字
        Citation c2 = c1.clone();

        c1.setName("张三");
        c2.setName("李四");

        // 3、调用show方法展示
        c1.show();
        c2.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

结果:

张三同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!
  • 1
  • 2

4.3.5、使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。

4.3.6、扩展(深克隆)

将上面的”三好学生“奖状的案例中Citation类的name属性修改为student类型的属性。代码如下:

// 奖状类
@Data
public class Citation implements Cloneable {

    private Student student;

    void show() {
        System.out.println(student.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生,特发此状!");
    }

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

@Data
public class Student {

    // 学生的姓名
    private String name;
}


public class CitationTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        // 1、创建原型对象
        Citation c1 = new Citation();
        // 创建张三学生对象
        Student stu = new Student();
        stu.setName("张三");
        c1.setStudent(stu);

        // 2、克隆奖状对象
        Citation c2 = c1.clone();
        c2.getStudent().setName("李四");


        // 3、调用show方法展示
        c1.show();
        c2.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

运行结果:Object的clone是浅拷贝

李四同学:在2020学年第一学期中表现优秀,被评为三好学生,特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生,特发此状!
  • 1
  • 2

说明:

c1的stu和c2的stu是同一个对象,就会产生将c1的stu对象中name属性值改为”李四“,两个Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。代码如下:

public class Student implements Serializable
public class Citation implements Cloneable, Serializable

public class CitationTest {

    public static void main(String[] args) throws Exception {
        // 1、创建原型对象
        Citation c1 = new Citation();
        // 创建张三学生对象
        Student stu = new Student();
        stu.setName("张三");
        c1.setStudent(stu);

        // 创建对象输出流对象
        Path path = Paths.get("b.txt");
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(path));
        // 写对象 序列化操作
        oos.writeObject(c1);
        // 释放资源
        oos.close(); // 这样就可以把 c1 对象序列化到 a.txt 文件中了

        // 读取对象
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(path));
        // 读取对象
        Citation c2 = (Citation) ois.readObject();
        // 释放资源
        ois.close();
        c2.getStudent().setName("李四");


        c1.show();
        c2.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

结果:

张三同学:在2020学年第一学期中表现优秀,被评为三好学生,特发此状!
李四同学:在2020学年第一学期中表现优秀,被评为三好学生,特发此状!
  • 1
  • 2

注意:Citation 类和 Student 类必须实现 Serializeable 接口,否则会抛 NotSerializableException 异常。

5、建造者设计模式

5.1、概述

将一个复杂对象的构建与表示分离(解耦合),使得同样的构建过程可以创建不同的表示。

在这里插入图片描述

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

5.2、结构

建造者(Builder)模式包含如下角色:强调的不是部件的创建,而是装配的过程

  • 抽象建造者(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品信息,只负责保证对象各部分完整创建或按某种顺序创建。

类图如下:

在这里插入图片描述

5.3、实例

创建共享单车

生产自行车是一个复杂的过程,它包含了车架、车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶、真皮等材质。对于自行车的生产就可以使用建造者模式。

这里的Bike是产品,包括车间、车座等组件;Builder是抽象建造者,MobileBuilder和ofoBuilder是具体的建造者;Director是指挥者。类图如下:

在这里插入图片描述

具体代码如下:

// 产品对象:自行车类
@Data
public class Bike {

    private String frame; // 车架
    private String seat; // 车座
}

// 抽象建造者类
public abstract class Builder {

    // 声明Bike类型的变量,并进行赋值,提高代码复用性
    protected Bike bike = new Bike();

    public abstract Bike createBike();

    public abstract void buildSeat();

    public abstract void buildFrame();
}

// 具体建造者类

// 用来构建摩拜单车对象
public class MobileBuilder extends Builder {
    @Override
    public void buildFrame() {
        bike.setFrame("碳纤维车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("真皮车座!");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

public class OfoBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("铝合金车架");
    }

    @Override
    public void buildSeat() {
        bike.setFrame("橡胶车座");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

// 指挥者类
public class Director {

    // 声明Builder类型的变量
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    // 组装自行车的功能
    public Bike construct() {
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

// 测试类
public class Client {

    public static void main(String[] args) {
        // 创建指挥者对象
        Director director = new Director(new MobileBuilder());
        // 让指挥者指挥组装自行车
        Bike bike = director.construct();

        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

注意:

上面示例是 Builder 模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造类进行结合。

// 抽象 builder 类
public abstract class Builder {

    protected Bike bike = new Bike();

    public abstract void buildFrame();

    public abstract void buildSeat();

    // 构建自行车的方法
    public abstract Bike createBike();
    
    public Bike construct() {
        this.buildFrame();
        this.buildSeat();
        return this.createBike();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

说明:

这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct()过于复杂,建议还是封装到 Director 中

5.4、优缺点

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新得建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

缺点:

建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

5.5、使用场景

建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的构造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

5.6、模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用构造者模式进行重构。

重构前代码如下:

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getMainboard() {
        return mainboard;
    }

    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

public class Client2 {

    public static void main(String[] args) {
        // 构建 Phone 对象
        Phone phone = new Phone("intel", "三星屏幕", "金士顿", "华硕");
        System.out.println("phone = " + phone);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

上面再客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高的。

重构后的代码:

public class Phone {

    private final String cpu;
    private final String memory;
    private final String screen;
    private final String mainboard;

    // 私有构造方法
    private Phone(Builder builder) {
        this.cpu = builder.cpu;
        this.memory = builder.memory;
        this.screen = builder.screen;
        this.mainboard = builder.mainboard;
    }

    public static final class Builder {
        private String cpu;
        private String memory;
        private String screen;
        private String mainboard;

        public Builder cpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public Builder memory(String memory) {
            this.memory = memory;
            return this;
        }

        public Builder screen(String screen) {
            this.screen = screen;
            return this;
        }

        public Builder mainboard(String mainboard) {
            this.mainboard = mainboard;
            return this;
        }

        // 使用构建者创建 Phone 对象
        public Phone build() {
            return new Phone(this);
        }
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", memory='" + memory + '\'' +
                ", screen='" + screen + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

public class Client {

    public static void main(String[] args) {
        // 创建手机对象  通过构建者对象获取手机对象
        Phone phone = new Phone.Builder()
                .cpu("cpu")
                .memory("金百达ddr5")
                .screen("三星屏幕")
                .mainboard("华硕ROG小吹雪B760G")
                .build();

        System.out.println(phone);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

此方式构建组件一目了然,要构建啥就调用啥方法,反正是链式编程。原视构建器模式的构建顺序由指挥者决定,而现在构建顺序交给了客户。

重构后的代码使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员要求比较高。

6、创建者模式对比

6.1、工厂方法模式VS建造者模式

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

6.2、抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品;具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程的,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/174196
推荐阅读
相关标签
  

闽ICP备14008679号