设计模式一

107 阅读38分钟

软件设计原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 6 条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本

开闭原则

对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。 想要达到这样的效果,我们需要使用接口和抽象类。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了

假如现在有一款输入法,用户可以随意切换皮肤,那么在设计软件结构时,就可以定义一个皮肤的抽象类,在类中定义输入法对外暴露的公共接口,如下代码

/**
 * 定义皮肤的抽象类
 *
 * @author dhj
 * @date 2022/9/4
 */
public abstract class AbstractSkin {
    /**
     * 定义显示皮肤的抽象方法
     */
    public abstract void display();
}

display()中的逻辑即输入法皮肤的渲染逻辑,不同的输入法需要实现自己的渲染逻辑

提供默认皮肤

/**
 * 默认的皮肤实现类,继承自抽象皮肤类 {@link AbstractSkin}
 *
 * @author dhj
 * @date 2022/9/4
 */
public class DefaultSkin extends AbstractSkin {
    @Override
    public void display() {
        System.out.println("default skin");
    }
}

提供红色背景的皮肤

/**
 * 红色背景的皮肤实现类,继承自 {@link AbstractSkin}
 *
 * @author dhj
 * @date 2022/9/4
 */
public class RedSkin extends AbstractSkin {
    @Override
    public void display() {
        System.out.println("red background skin");
    }
}

RedSkinDefaultSkin它们实现的都是抽象的AbstractSkin,这能保证不论有多少种不同皮肤实现类,在实际应用时,都只需要统一调用display(),如下代码

/**
 * 搜狗输入法具体皮肤的应用类,主要用于将不同的皮肤实现类渲染出来
 *
 * @author dhj
 * @date 2022/9/4
 */
public class SougouInput {
    // 定义一个类型为 AbstractSkin 的属性,目的在兼容其任意实现类
    private AbstractSkin skin;

    public void setSkin(AbstractSkin skin) {
        this.skin = skin;
    }

    public void display() {
        skin.display();
    }
}

在实际应用输入法时,只需要声明private AbstractSkin skin;即可兼容所有的皮肤实现类

最终在客户端使用SougoInput

/**
 * 定义客户端,使用搜狗输入法
 *
 * @author dhj
 * @date 2022/9/4
 */
public class Client {
    public static void main(String[] args) {
        // 创建搜狗输入法的实例
        SougouInput sougouInput = new SougouInput();
        // 传入 AbstractSkin 的具体实现类(首次使用默认皮肤)
        sougouInput.setSkin(new DefaultSkin());
        // 调用公共的 display 接口,即可实现任意子类的皮肤渲染逻辑
        sougouInput.display();

        // 后续客户端如果要更换皮肤,只需要重新设置对应皮肤的实现类即可
        sougouInput.setSkin(new RedSkin());
        sougouInput.display();// 重新渲染皮肤
    }
}

如果说还需要其他类型的皮肤,则不应该修改现有的DefaultSkinRedSkin两个类,应该继续实现AbstractSkin类,重写其中display() 方法来自定义扩展其他类型的皮肤;这就是开闭原则的体现(对扩展开放,对修改关闭)

里氏替换原则

里氏代换原则是面向对象设计的基本原则之一

里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能, 但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。 如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。

下面来看一个例子

现在要定义一个正方向和长方形,从数学角度来说,正方向也是长方形的一种,那么为了方便代码复用,可以定义长方形类,正方形类只需要实现即可,不论是从数学常识还是代码的实现角度来说,都很符合逻辑

长方形定义如下

/**
 * 长方形类
 *
 * @author dhj
 * @date 2022/9/4
 */
public class Rectangle {
    private double length;
    private double width;

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }
}

正方向定义如下,其继承了Rectangle

/**
 * 正方形类
 *
 * @author dhj
 * @date 2022/9/4
 */
public class Square extends Rectangle {

    @Override
    public void setLength(double length) {
        super.setLength(length);
        super.setWidth(length);
    }

    @Override
    public void setWidth(double width) {
        super.setLength(width);
        super.setWidth(width);
    }
}

注意,这里子类Square重写了父类Rectangle中的setLength()setWidth(),这是不遵循里氏替换原则的,如果在后续父类Rectangle出现的地方出现了Square,可能会出现问题,这在后续代码中会体现出来

调用测试类,创建一个Rectangle实例进行测试

/**
 * @author dhj
 * @date 2022/9/4
 */
public class RectangleDemo {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(5);
        rectangle.setLength(10);
        resize(rectangle);
        printWidthAndRectangle(rectangle);
    }

    /**
     * 当检查到长方形的 width <= length 时,对 width 进行增长
     *
     * @param rectangle 接收一个 Rectangle 实例
     */
    public static void resize(Rectangle rectangle) {
        while (rectangle.getWidth() <= rectangle.getLength()) {
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }

    /**
     * 打印 Rectangle 的 width 和 length
     *
     * @param rectangle 接收一个 Rectangle 实例
     */
    public static void printWidthAndRectangle(Rectangle rectangle) {
        System.out.println("width:" + rectangle.getWidth() + "\n" + "length:" + rectangle.getLength());
    }
}

注意,在main方法中,这里传入到resize()中的rectangle本身就是一个Rectangle类型的

下面将Square实例传入到resize()中,对main方法进行改造,如下代码

public static void main(String[] args) {
    Square square = new Square();
    square.setWidth(10);
    resize(square);
    printWidthAndRectangle(square);
}

执行后会发现,程序无法停止运行,这里建议再回过头去观察Square中对父类Rectangle中相关方法的重写就能得出程序无法停止的原因了

并且,resize()方法在设计之初,也是针对Rectangle中相关方法的逻辑来实现的,子类Square既然修改了Rectangle中的逻辑,那就会存在问题出现的风险

以上问题是不遵循里氏替换原则的一个经典体现,即:在resize(Rectangle rectangle)的方法参数中,也就是基类Rectangle出现的地方,子类Square不一定可以出现,原因在于子类重写了父类中的相关方法,而不是去扩展新方法

要想解决这个问题,需要定义一个更抽象的接口,将RectangleSquare之间的继承关系断开,二者应该实现公共的抽象接口,公共接口定义如下

/**
 * 定义一个四边形接口
 *
 * @author dhj
 * @date 2022/9/4
 */
public interface Quadrilatera {

    /**
     * 定义四边形获取长度的接口方法,只要是四边形就有长度
     *
     * @return 返回长度
     */
    double getLength();

    /**
     * 定义四边形获取宽度的接口方法,只要是四边形就有宽度
     *
     * @return 返回宽度
     */
    double getWidth();
}

定义长方形

/**
 * 定义长方形,实现 Quadrilatera 接口
 *
 * @author dhj
 * @date 2022/9/4
 */
public class Rectangle implements Quadrilatera {
    private double length;
    private double width;

    public void setLength(double length) {
        this.length = length;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double getLength() {
        return this.length;
    }

    @Override
    public double getWidth() {
        return this.width;
    }
}

定义正方形

/**
 * @author dhj
 * @date 2022/9/4
 */
public class Square implements Quadrilatera {
    private double side;

    @Override
    public double getLength() {
        return this.side;
    }

    @Override
    public double getWidth() {
        return this.side;
    }
}

注意,这里的正方形不再需要再去重写父类中的某些方法了,因为SquareRectangle都实现了公共的接口Quadrilatera,在Quadrilatera中定义的都是更为抽象的接口方法,SquareRectangle都适用,二者只需要根据自己的情况实现对应接口方法即可

例如这里的Square,因为正方形无论长宽都相等,因此可以直接定义一个side变量,但是getLength()getWidth()这两个接口方法却不受影响,因为这两个接口方法的定义足够抽象【任何四边形都有长宽】

下面是测试类

public class RectangleDemo {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(10);
        rectangle.setLength(20);
        resize(rectangle);
        printWidthAndRectangle(rectangle);
    }

    /**
     * 当检查到长方形的 width <= length 时,对 width 进行增长
     *
     * @param rectangle 接收一个 Rectangle 实例
     */
    public static void resize(Rectangle rectangle) {
        while (rectangle.getWidth() <= rectangle.getLength()) {
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }

    /**
     * 打印 Rectangle 的 width 和 length
     *
     * @param rectangle 接收一个 Rectangle 实例
     */
    public static void printWidthAndRectangle(Rectangle rectangle) {
        System.out.println("width:" + rectangle.getWidth() + "\n" + "length:" + rectangle.getLength());
    }
}

经过上述结构性的变化,resize()方法的参数将不再支持传入Square类型的示例,因为resize()方法的参数明确声明了需要Rectangle类型,而单论SquareRectangle之间并无派生或继承的关系;除非方法的参数为Quadrilatera类型,因为Quadrilatera是二者的公共接口

既然resize()无法传入Square类型的实例,也就不存在程序无法停止的问题了

依赖倒转原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,通俗来说,就是依赖关系之间,具备通用性,例如电脑主板上的 cpu 插槽和具体的不同品牌的 cpu 之间就具备依赖关系,而主板的插槽也需要具备一定的通用性,也就是说插槽 ---> CPU而不是插槽 ---> Intel、AMD等具体的 cpu,这就是依赖其抽象

下面来看一个文件上传的例子

在实际的开发中,对于一些静态资源的存储,多会选择云存储,优点在于简单高效,易于维护,但可能有时候项目中不只使用一种云存储,某些情况下可能会使用百度云,也可能会使用阿里云,这就需要设计一个抽象接口,在其中定义文件上传存储的抽象方法,用于适配不同的云存储方案,如下

/**
 * @author dhj
 * @date 2022/9/5
 */
public interface UploadService {
    /**
     * 上传文件
     *
     * @param file 需要上传的目标文件
     * @return 返回上传的 url
     */
    String uploadFile(File file);
}

有了抽象接口,不同的云存储方案只需要实现此接口,然后编写各自的云存储逻辑即可,如下代码

阿里云文件上传

/**
 * @author dhj
 * @date 2022/9/5
 */
public class AliyunUploadService implements UploadService {
    @Override
    public String uploadFile(File file) {
        return RandomUtil.randomString(12);
    }
}

百度云文件上传

/**
 * @author dhj
 * @date 2022/9/5
 */
public class BaiduUploadService implements UploadService {
    @Override
    public String uploadFile(File file) {
        return RandomUtil.randomString(10);
    }
}

在具体使用时,需要注意,应该依赖抽象的UploadService接口

/**
 * @author dhj
 * @date 2022/9/5
 */
public class Demo {
    public static void main(String[] args) {
        // 依赖接口, 也就是依赖其抽象的体现
        UploadService uploadService = new BaiduUploadService();
        String url = uploadService.uploadFile(new File(""));
        System.out.println(url);
    }
}

依赖抽象的好处在于,后续如果需要更换云存储,只需要替换为对应的实例即可,又因为接口方法uploadFile()的统一定义,就无需再去修改具体的方法签名

但这样通过new方式来指定具体实现的方式依然具备一定耦合度,后续如果要更换对应接口的实现,需要在每个new关键字处去修改调用的构造方法,例如需要将每个new BaiduUploadService()替换为new AliyunUploadService()

这实际上就深刻的体现出了Spring中控制反转的优点,在Spring中,只需要申明对应的依赖关系,例如

// 申明一个 UploadService 依赖
UploadService uploadService;

至于依赖关系的初始化等操作,都交由Spring管理,而无需自己通过new去创建和指定对应的依赖实例,这就达到将所有依赖关系全都抽象化,高度解耦的效果,是依赖倒转原则的深刻体现

接口隔离原则

一个类对另一个类的依赖应该建立在最小的接口上,而不应该被迫依赖其不使用的方法,如下图

迪米特法则

迪米特法则又叫最少知识原则。 只和你的【朋友】交谈,不跟【陌生人】说话,其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性

迪米特法则中的【朋友】是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

迪米特法则就好比租客、房东、中介之间的关系,如下图

除此之外【明星 => 经纪人】也能够很好的解释迪米特法则,明星拥有粉丝,明星还需要和传媒公司签约,但明星和粉丝之间的见面以及与公司的签约都不会由明星自己去安排,一般会交由经纪人,【经纪人】就是迪米特法则中的第三方,如下代码

明星类

/**
 * 明星类
 *
 * @author dhj
 * @date 2022/9/7
 */
public class Star {
    private String name;

    public Star(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

粉丝类

/**
 * 粉丝类
 *
 * @author dhj
 * @date 2022/9/7
 */
public class Fans {
    private String name;

    public Fans(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

传媒公司

/**
 * 媒体公司类
 *
 * @author dhj
 * @date 2022/9/7
 */
public class Company {
    private String name;

    public Company(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
/**
 * 经纪人类
 *
 * @author dhj
 * @date 2022/9/7
 */
public class Agent {
    private Star star;

    private Fans fans;

    private Company company;

    public Agent(Star star, Fans fans, Company company) {
        this.star = star;
        this.fans = fans;
        this.company = company;
    }

    /**
     * 安排明星和粉丝见面
     */
    public void meeting() {
        System.out.println("【" + star.getName() + "】" + "和【" + fans.getName() + "】见面");
    }

    /**
     * 与媒体公司的商业洽谈
     */
    public void business() {
        System.out.println("【" + star.getName() + "】" + "和【" + company.getName() + "】签约");
    }
}

关键在于Agent类,它将Star Fans Compay这三个不相关的类作为自身的依赖属性,然后在对应的方法中,调用这些属性,完成具体的业务逻辑,至始至终,Star Fans Compay 之间没有直接的关联,达到解耦的效果

可以想象在实际业务开发中,涉及到不同业务或模块之间的交互但又不想产生耦合,或许可以参考参考迪米特法则

合成复用原则

合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

尽管继承的方式能够提高代码的复用性,但也存在一些缺点

  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为白箱复用
  • 子类与父类的耦合度高。父类实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护
  • 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化,也就是不可能在代码运行时,改变这种继承关系

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点

  • 维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为黑箱复用
  • 对象间的耦合度低。可以在类的成员位置声明抽象。
  • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地重新引用与组合对象类型相同的对象

比如现在有一个Car类,按照类型,可以分为【汽油汽车】和【电动汽车】,按照颜色可划分为【白色】和【黑色】

按照继承的方式,可能会有如下结构的类

以上结构,每新增一种类型的汽车或颜色,都需要定义新的类

如果采用合成复用的原则,如下图

Car类中,直接将汽车的颜色定义成接口并作为一个属性组合到Car中,使用时直接传入对应的颜色属性实例,Car的子类PetrolCarElectricCar就不用再单独创建对应颜色的子类

设计模式之创建型模式

创建型模式的主要关注点是 怎样创建对象?,它的主要特点是 将对象的创建与使用分离, 这样可以降低系统的耦合度,使用者不需要关注对象的创建细节

创建型模式分为

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

下面将一一分析

单例模式

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

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建

这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

单例模式分为两种

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

饿汉式

首先是饿汉式,饿汉式中,将单实例成员变量申明为静态私有的,并且单例类的构造方法也被申明为私有,通过公共的静态get方法来获取单实例成员变量,static类型的成员变量只会在类加载时初始化一次,符合饿汉单例的特性,如下代码

/**
 * 饿汉单例模式
 *
 * @author dhj
 * @date 2022/9/8
 */
public class SingletonHungry {
    private static final SingletonHungry INSTANCE = new SingletonHungry();

    private SingletonHungry() {

    }

    public static SingletonHungry getINSTANCE() {
        return INSTANCE;
    }
}

饿汉单例静态代码块形式,如下代码

/**
 * 饿汉单例模式
 *
 * @author dhj
 * @date 2022/9/8
 */
public class SingletonHungry2 {
    private static final SingletonHungry2 INSTANCE;

    // 在静态代码块中对单实例初始化
    static {
        INSTANCE = new SingletonHungry2();
    }

    private SingletonHungry2() {

    }

    public static SingletonHungry2 getINSTANCE() {
        return INSTANCE;
    }
}

饿汉式的缺点在于,就算没有使用单实例,也会被加载到内存中,造成内存空间的浪费

饿汉单例模式(枚举)

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次

枚举单例是所有单例实现中唯一一种不会被破坏的单例实现模式

/**
 * 饿汉单例模式(枚举实现)
 *
 * @author dhj
 * @date 2022/9/8
 */
public enum SingleEnumHungry {
    INSTANCE
}

懒汉式

线程不安全的懒汉单例

/**
 * 懒汉单例模式
 *
 * @author dhj
 * @date 2022/9/8
 */
public class SingletonLazy1 {
    private static SingletonLazy1 INSTANCE;


    private SingletonLazy1() {

    }

    // 多线程访问时, 可能出现同时判断到 INSTANCE 为 null 的情况, 造成多次创建单实例, 破坏了单例原则 
    public static SingletonLazy1 getINSTANCE() {
        if (INSTANCE == null) {
            INSTANCE = new SingletonLazy1();
        }
        return INSTANCE;
    }
}

线程安全的懒汉单例模式

/**
 * 懒汉单例模式
 *
 * @author dhj
 * @date 2022/9/8
 */
public class SingletonLazy1 {
    private static SingletonLazy1 INSTANCE;


    private SingletonLazy1() {

    }

    // 加锁则可以保证线程安全
    public static synchronized SingletonLazy1 getINSTANCE() {
        if (INSTANCE == null) {
            INSTANCE = new SingletonLazy1();
        }
        return INSTANCE;
    }
}

懒汉单例模式(双检锁)

/**
 * @author dhj
 * @date 2022/9/8
 */
public class DoubleCheckLockLazySingleton {

    // volatile 修饰, 禁指令重排序, 避免因指令重排序可能导致的因【变量初始化指令乱序】引发的线程安全问题(空指针)
    private volatile static DoubleCheckLockLazySingleton INSTANCE;

    public static DoubleCheckLockLazySingleton getINSTANCE() {
        // 无锁检查, 不为空可直接返回单实例, 不影响多线程读取的性能
        if (INSTANCE == null) {
            // 单实例为 null, 进入单实例的创建逻辑, 需要加锁, 保证线程安全
            synchronized (DoubleCheckLockLazySingleton.class) {
                /*
                 可能存在第一次检查都为 true 的情况, 造成多个线程竞争锁
                 首次竞争到锁的线程创建初始化单实例成功后, 后续竞争到锁的线程需要再次检查
                 */
                if (INSTANCE == null) {
                    INSTANCE = new DoubleCheckLockLazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

懒汉单例模式(静态内部类)

/**
 * @author dhj
 * @date 2022/9/8
 */
public class SingletonLazyStaticInnerClass {
    private SingletonLazyStaticInnerClass() {
    }

    // 静态内部类只会在首次调用时才会被加载且只会加载一次(由 JVM 保证),符合懒汉单例模式的原则
    private static class SingletonHolder {
        private static final SingletonLazyStaticInnerClass INSTANCE = new SingletonLazyStaticInnerClass();
    }

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

序列化破坏单例模式

定义单例

/**
 * 序列化破坏单例模式
 * 以下使用饿汉式(静态代码块)的单例形式来演示
 *
 * @author dhj
 * @date 2022/9/9
 */
public class SerializationDestroySingleton implements Serializable {

    private static final SerializationDestroySingleton INSTANCE;

    static {
        INSTANCE = new SerializationDestroySingleton();
    }

    private SerializationDestroySingleton() {
    }

    public static SerializationDestroySingleton getINSTANCE() {
        return INSTANCE;
    }
}

使用序列化破坏单例

/**
 * @author dhj
 * @date 2022/9/9
 */
public class SerializationDestroySingletonDemo {
    public static void main(String[] args) {
        // 序列化
        writeToFile();
        // 反序列化
        SerializationDestroySingleton readSingleton = readFromFile();
        // 输出 false, 这说明通过序列化以及反序列化可以创建多个单实例对象, 破坏了单例原则
        System.out.println(SerializationDestroySingleton.getINSTANCE() == readSingleton);
    }

    public static SerializationDestroySingleton readFromFile() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("D:\\INSTANCE.txt"));
            return (SerializationDestroySingleton) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static void writeToFile() {
        ObjectOutputStream oos = null;
        try {
            SerializationDestroySingleton instance = SerializationDestroySingleton.getINSTANCE();
            oos = new ObjectOutputStream(new FileOutputStream("D:\\INSTANCE.txt"));
            oos.writeObject(instance);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

解决办法

/**
 * 序列化破坏单例模式
 * 以下使用饿汉式(静态代码块)的单例形式来演示
 *
 * @author dhj
 * @date 2022/9/9
 */
public class SerializationDestroySingleton implements Serializable {

    private static final SerializationDestroySingleton INSTANCE;

    static {
        INSTANCE = new SerializationDestroySingleton();
    }

    private SerializationDestroySingleton() {
    }

    public static SerializationDestroySingleton getINSTANCE() {
        return INSTANCE;
    }

    /**
     * 解决序列化反序列化破解单例模式
     */
    private Object readResolve() {
        return getINSTANCE();
    }
}

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

反射破坏单例模式

/**
 * @author dhj
 * @date 2022/9/9
 */
public class ReflectionDestorySingletonDemo {
    public static void main(String[] args) {
        try {
            ReflectionDestorySingleton singleton = ReflectionDestorySingleton.getINSTANCE();
            
            // 反射获取私有的构造方法
            Class<ReflectionDestorySingleton> clazz = ReflectionDestorySingleton.class;
            Constructor<ReflectionDestorySingleton> constructor = clazz.getDeclaredConstructor();
            // 设置私有构造方法的访问权限
            constructor.setAccessible(true);
            // 使用反射创建新的实例
            ReflectionDestorySingleton refSingleton = constructor.newInstance();
            // 返回值为 false, 说明可以使用反射获取多个不同的单实例, 破坏了单例原则
            System.out.println(refSingleton == singleton);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解决办法

/**
 * @author dhj
 * @date 2022/9/9
 */
public class ReflectionDestorySingleton {
    private static final ReflectionDestorySingleton INSTANCE;

    static {
        INSTANCE = new ReflectionDestorySingleton();
    }

    // 在单实例的构造函数中, 直接抛出异常, 禁止反射调用创建新的实例
    private ReflectionDestorySingleton() {
        throw new RuntimeException("单例模式下非法调用构造函数");
    }

    public static ReflectionDestorySingleton getINSTANCE() {
        return INSTANCE;
    }
}

工厂模式

在 Java 中,随时随地都在 new 对象,但如果每次使用到该对象时,都通过 new 方式来创建,那么后续要想更换此对象,那么在 new 此对象的位置都要修改,严重耦合

如果设计一种工厂类来生产对象,在用到此对象的地方,调用工厂方法就可以了,将对象的创建和使用分离,如果要更换对象, 直接在工厂里更换该对象即可,达到了与对象解耦的目的

所以,工厂模式最大的优点就是:解耦

简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯,简单工厂包含以下角色

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

例如有一汽车抽象类

/**
 * 抽象 Car 类
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractCar {
    abstract String getName();
}

子类


/**
 * 奔驰汽车
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class BenzCar extends AbstractCar {
    private final String name;

    public BenzCar(String name) {
        this.name = name;
    }

    @Override
    String getName() {
        return this.name;
    }
}
/**
 * 法拉利汽车
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class FerrariCar extends AbstractCar {

    private final String name;

    public FerrariCar(String name) {
        this.name = name;
    }

    @Override
    String getName() {
        return this.name;
    }
}

对于这种具有多个实现类的结构,就可以定义一个简单工厂,用于创建所需的子类对象,至于创建哪个子类对象,则给定的类型参数决定,如下

/**
 * @author dhj
 * @date 2022/9/12
 */
public class SimpleCarFactory {

    /**
     * 创建 AbstractCar 类型的实例
     *
     * @param type 类型参数
     * @return 返回指定类型参数对应的子类实例,没有则返回 null
     */
    public static AbstractCar createCar(Integer type) {
        switch (type) {
            case 1:
                return new BenzCar("奔驰");
            case 2:
                return new FerrariCar("法拉利");
            default:
                return null;
        }
    }
}

通过这样一种方式,在使用具体的子类对象时,只需要统一调用工厂中的创建方法即可,如要替换某一子类,也只需要修改工厂方法中对应的返回值即可

简单工厂的缺点

目前使用工厂模式,将对象的创建和具体的使用分离,减少了耦合,但是,工厂类依然与具体的创建对象耦合,如果需要增加新的实现,则需要直接修改工厂方法,违背了开闭原则

工厂方法模式

工厂方法模式解决上述简单工厂违反开闭原则的问题,工厂方法模式定义了一个抽象工厂,抽象工厂中定义了创建产品的接口,而抽象工厂的实现类叫做具体工厂,对应不同的创建产品的方式,工厂方法模式将创建产品的行为延迟到了子实现类来执行,这使得创建产品时,具有更强的灵活性,因为实现类可以新增,针对不同的产品都可以定义不同的子实现类

工厂方法主要有以下角色

  • 抽象工厂,定义创建产品的抽象接口
  • 具体工厂,实现抽象工厂中的抽象接口
  • 抽象产品,定义产品的抽象接口
  • 具体产品,不同的产品可以自定义额外的属性和方法

抽象产品类

/**
 * 抽象汽车类
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractCar {

    abstract String getName();
}

具体产品类

/**
 * 奔驰汽车
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class BenzCar extends AbstractCar {
    private final String name;

    public BenzCar(String name) {
        this.name = name;
    }

    @Override
    String getName() {
        return this.name;
    }
}
/**
 * 法拉利汽车
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class FerrariCar extends AbstractCar {

    private final String name;

    public FerrariCar(String name) {
        this.name = name;
    }

    @Override
    String getName() {
        return this.name;
    }
}

抽象汽车工厂

/**
 * 抽象汽车工厂
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractCarFacotry {

    abstract AbstractCar createCar();
}

具体工厂

/**
 * 奔驰汽车具体工厂类
 *
 * @author dhj
 * @date 2022/9/12
 */
public class BenzCarFactory extends AbstractCarFacotry {

    @Override
    AbstractCar createCar() {
        return new BenzCar("奔驰汽车");
    }
}

/**
 * 法拉利汽车具体工厂类
 *
 * @author dhj
 * @date 2022/9/12
 */
public class FerrariCarCarFactory extends AbstractCarFacotry {
    @Override
    AbstractCar createCar() {
        return new FerrariCar("法拉利汽车");
    }
}

工厂方法模式的好处在于,如有要新增产品类型,无需再去更改对应的具体工厂方法,只需要新增一个抽象工厂的实现类,在其中定义新产品类型的创建逻辑,这完美符合了开闭原则

但缺点在于,每新增一个产品,就需要创建新的实现类,增加了系统复杂度

抽象工厂模式

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

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

使用场景

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

以家具厂为例,家具厂生产桌子、椅子、沙发,这三者可以看作一个产品族,而这些家具又分为现代风格和古典风格,那么可以将其分为两个系列

  • 现代风格的家具
  • 古典风格的家具

那么根据不同的系列,提供不同的工厂类,在其中定义统一的桌子、椅子、沙发这一产品族的创建方法,确保同一个工厂类创建出的家具是统一风格的,示例代码如下

抽象工厂

/**
 * 抽象工厂类
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractFurnitureFactory {

    /**
     * 桌子创建接口
     *
     * @return 返回具体的桌子实例
     */
    public abstract AbstractDesk createDesk();


    /**
     * 椅子创建接口
     *
     * @return 返回具体的椅子实例
     */
    public abstract AbstractChair createChair();


    /**
     * 沙发创建接口
     *
     * @return 返回具体的沙发实例
     */
    public abstract AbstractSofa createSofa();
}

抽象产品类

/**
 * 抽象椅子类
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractChair {
   public abstract String getName();
}
/**
 * 抽象桌子类
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractDesk {
   public abstract String getName();
}
/**
 * 抽象沙发类
 *
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractSofa {
    public abstract String getName();
}

具体产品类(现代风格)

/**
 * 现代风格椅子
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class ModernStyleChair extends AbstractChair {
    private final String name;


    public ModernStyleChair(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
/**
 * 现代风格桌子
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class ModernStyleDesk extends AbstractDesk {
    private final String name;


    public ModernStyleDesk(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
/**
 * 现代风格沙发
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class ModernStyleSofa extends AbstractSofa {
    private final String name;


    public ModernStyleSofa(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

具体产品(古典风格)

/**
 * 古典风格椅子
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class ClassicalChair extends AbstractChair {

    private final String name;

    public ClassicalChair(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
/**
 * 古典风格桌子
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class ClassicalDesk extends AbstractDesk {

    private final String name;

    public ClassicalDesk(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}
/**
 * 古典风格沙发
 *
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class ClassicalSofa extends AbstractSofa {

    private final String name;

    public ClassicalSofa(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

具体工厂

/**
 * 古典家具工厂
 *
 * @author dhj
 * @date 2022/9/12
 */
public class ClassicalFurnitureFacoty extends AbstractFurnitureFactory {
    @Override
    public AbstractDesk createDesk() {
        return new ClassicalDesk("古典风格桌子");
    }

    @Override
    public AbstractChair createChair() {
        return new ClassicalChair("古典风格椅子");
    }

    @Override
    public AbstractSofa createSofa() {
        return new ClassicalSofa("古典风格沙发");
    }
}
/**
 * 现代家具工厂
 *
 * @author dhj
 * @date 2022/9/12
 */
public class ModernFurnitureFacoty extends AbstractFurnitureFactory {
    @Override
    public AbstractDesk createDesk() {
        return new ModernStyleDesk("现代风格桌子");
    }

    @Override
    public AbstractChair createChair() {
        return new ModernStyleChair("现代风格椅子");
    }

    @Override
    public AbstractSofa createSofa() {
        return new ModernStyleSofa("现代风格沙发");
    }
}

上述代码的结构关系图如下

抽象工厂能够保证使用者能够始终使用同一产品族中的对象

但抽象工厂的缺点也很明显,当一个产品族中需要新增产品时,对应的抽象工厂都需要改变

简单工厂 + 配置文件解耦

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

如下代码

抽象产品类

/**
 * @author dhj
 * @date 2022/9/12
 */
public abstract class AbstractCar {
    public abstract String getName();
}

具体产品类

/**
 * @author dhj
 * @date 2022/9/12
 */
@ToString
public class BenzCar extends AbstractCar {
    private final String name;

    public BenzCar(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
}
/**
 * @author dhj
 * @date 2022/9/12
 */
public class FerrariCar extends AbstractCar{
    private final String name;

    public FerrariCar(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
}

对应的配置文件

BenzCar=example.mode.creator.factory.properties_factory.BenzCar-奔驰汽车
FerrariCar=example.mode.creator.factory.properties_factory.FerrariCar-法拉利汽车

工厂类

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author dhj
 * @date 2022/9/12
 */
public class SimpleFactory {
    private static final Map<String, AbstractCar> beans = new ConcurrentHashMap<>(16);

    static {
        final Properties properties = new Properties();
        InputStreamReader classNameFileStream = null;
        try {
            // 加载配置文件
            InputStream propertiesResource = SimpleFactory.class.getClassLoader().getResourceAsStream("className.properties");
            if (propertiesResource == null) {
                throw new RuntimeException("配置文件加载错误");
            }
            // 加载配置文件流(StandardCharsets.UTF_8 用于处理乱码问题)
            classNameFileStream = new InputStreamReader(propertiesResource, StandardCharsets.UTF_8);
            properties.load(classNameFileStream);
            // 遍历配置项
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                // 获取类名
                String className = (String) entry.getKey();
                // 获取类路径以及构造方法参数
                String classPathAndParam = entry.getValue().toString();
                String[] values = classPathAndParam.split("-");
                // 加载指定类路径的 class 实例
                Class<?> beanClass = Class.forName(values[0]);
                // 反射获取有参构造方法实例
                Constructor<?> constructor = beanClass.getDeclaredConstructor(String.class);
                // 使用配置文件中指定的参数,调用构造方法创建实例对象
                AbstractCar car = (AbstractCar) constructor.newInstance(values[1]);
                beans.put(className, car);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (classNameFileStream != null) {
                try {
                    classNameFileStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static AbstractCar createCar(String className) {
        return beans.get(className);
    }
}

测试代码

/**
 * @author dhj
 * @date 2022/9/12
 */
public class MainDemo {
    public static void main(String[] args) {
        AbstractCar benzCar = SimpleFactory.createCar("BenzCar");
        System.out.println(benzCar);
    }
}

JDK 中的工厂方法模式

在 jdk 中也有很多地方用到了工厂方法模式,例如集合获取迭代器的方法,如下代码

List<String> list = new ArrayList<>(10);
list.iterator(); // 通过 iterator() 创建 Iterator 实例

其中

  • 抽象工厂为java.lang.Iterable为抽象工厂类,因为iterator()方法在其中定义,
  • 具体工厂为java.util.ArrayList类,因为java.util.ArrayList间接实现了java.lang.Iterable接口,实现了iterator()方法
  • 抽象产品为java.util.Iterator
  • 具体产品为java.util.ArrayList.Itr

除了迭代器使用到了工厂模式,jdk 中还有

  • DateForamt 类中的 getInstance() 方法使用的是工厂模式
  • Calendar 类中的 getInstance() 方法使用的是工厂模式

原型模式

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

原型模式包含如下角色

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

原型模式的克隆又分为

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址

浅克隆

在 jdk 中,已经提供了抽象的原型类java.lang.Cloneable,官方文档定义如下

一个类实现了 Cloneable 接口,以向 Object.clone() 方法指示该方法可以合法地对该类的实例进行逐个字段的复制

在未实现 Cloneable 接口的实例上调用 Object 的 clone 方法会导致抛出异常 CloneNotSupportedException

java.lang.Cloneable中并没有提供抽象的clone()方法,此方法实际上在java.lang.Object被定义,是每个对象都具备的方法,但要想合法的调用此方法,就如官方文档所说,需要实现java.lang.Cloneable接口,详细代码演示如下

直接定义具体的原型类

/**
 * @author dhj
 * @date 2022/9/13
 */
@ToString
public class RealizetypeClone implements Cloneable {

    private final String name;

    public RealizetypeClone(String name) {
        this.name = name;
        System.out.println("原型对象创建完成");
    }

    @Override
    public RealizetypeClone clone() {
        try {
            return (RealizetypeClone) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

测试类

/**
 * @author dhj
 * @date 2022/9/13
 */
public class MainDemo {
    public static void main(String[] args) {
        RealizetypeClone clone = new RealizetypeClone("zhangsan");
        RealizetypeClone r1 = clone.clone();
        RealizetypeClone r2 = clone.clone();

        System.out.println(r1);
        System.out.println(r2);
    }
}

简单来说,原型模式的作用在于定义一个模板对象,而不同模板对象的区别主要使用构造函数进行区分,例如某些属性不同,在调用构造函数时传入,而其他相同的属性或方法后续再使用时,则可以直接通过clone()方法复用

例如在实际业务中,有一个短信发送的模板对象,每次发送短信时都需要传入手机号,姓名,内容,而内容格式大多数情况下是固定不变的,只需要通过构造方法传入手机号和姓名,创建一个具体原型对象,复用时,直接调用其clone()方法即可

深克隆

浅克隆只会拷贝原型对象,而原型对象中的引用类型的属性则会直接将其引用地址赋给克隆对象中的对应属性,这就导致当修改其中一个克隆对象中的引用类型属性时,其余的克隆对象也会跟着发生改变

这种情况就需要使用到深克隆,将原型对象中的引用类型属性也克隆一份新的到克隆对象中,假设有一个奖状类,类中包含奖状的展示方法以及对应的学生,使用深拷贝实现的具体代码如下

奖状具体原型类

/**
 * @author dhj
 * @date 2022/9/13
 */
@ToString
@Getter
public class Certificate implements Serializable {

    private final Student stu;

    public Certificate(Student stu) {
        this.stu = stu;
    }

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

    // 使用 json 序列和反序列化的方式,进行深度克隆
    public Certificate deepClone() {
        return JSONObject.parseObject(JSONObject.toJSONString(this), this.getClass());
    }

    @ToString
    @Data
    static class Student implements Serializable {
        private String name;

        public Student(String name) {
            this.name = name;
        }
    }
}

测试类

/**
 * @author dhj
 * @date 2022/9/13
 */
public class MainDemo {
    public static void main(String[] args) {
        Certificate template = new Certificate(new Certificate.Student("zhangsan"));

        Certificate certificate = template.deepClone();

        Certificate certificate1 = template.deepClone();
        // 某一克隆对象对自己引用属性的修改不会影响其他克隆对象
        certificate1.getStu().setName("lisi");

        certificate.show();
        certificate1.show();
    }
}

深克隆的方式除了 json 序列化的方式外,还可以使用 jdk 自带的对象序列化,这里为了方便使用的的是 json 序列化

建造者模式

建造者模式,又称为生成器模式,是一种创建型设计模式,能够分步骤创建复杂对象,该模式允许使用相同的创建代码生成不同类型和形式的对象

什么叫复杂对象?在对复杂对象进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作,这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;又或者这些代码散落在客户端代码的多个位置

建造者模式一般包含如下角色

  • 抽象建造者,声明建造某一产品的通用步骤
  • 具体建造者,负责具体实现抽象生成器中定义的步骤
  • 产品,最终生成的对象
  • 主管,使用生成器,定义不同的建造方式或步骤,用以创建不同的产品
  • 客户端,客户端可以选择调用主管来创建已经定义好的建造步骤;又或者直接调用建造者,自定义建造的步骤,最终生成客户端需要的产品

可以将建造者模式想象成一个工厂

  • 一条流水线上定义的机械化步骤,代表【抽象建造者】
  • 流水线上的工人负责维护和操作流水线,代表【具体建造者】
  • 流水线最终生产的产品,代表建造者模式中的【产品】
  • 工厂里面的技术主管,负责调整或协调整个流水线的机械化步骤,代表建造者模式中的【主管】
  • 客户端则可以理解为有产品生成需求的客户,例如有家具公司需要生产一批家具,家具公司就可以理解为【客户端】

假设现在以生产笔记本电脑为例,可以划分出的步骤有生产 CPU 生产内存 生产硬盘 生产主板 生产显卡 生产屏幕 生产键盘等,这其中有些步骤不是必须的,例如生产显卡,一些轻薄本不配置独立显卡;而各个步骤的具体生产逻辑也可能不一样,配件的型号不同等,适合使用建造者模式,具体代码实现如下

抽象建造者定义

/**
 * 笔记本电脑抽象建造者, 定义笔记本产品建造的各项步骤
 *
 * @author dhj
 * @date 2022/9/13
 */
public abstract class Builders {
    public abstract void builderCPU(String name);

    public abstract void builderDisk(String name);

    public abstract void builderKeyboard(String name);

    public abstract void builderMemory(String name);

    public abstract void builderMontherboard(String name);

    public abstract void builderScreen(String name);

    // 产品的建造步骤执行完成后, 调用此方法组合最终产品
    public abstract Computer finished();
}

具体建造者实现

/**
 * Rog牌电脑建造者
 *
 * @author dhj
 * @date 2022/9/13
 */
public class RogBuilder extends Builders {

    private CPU cpu;
    private Disk disk;
    private Keyboard keyboard;
    private Memory memory;
    private Motherboard motherboard;
    private Screen screen;

    @Override
    public void builderCPU(String name) {
        this.cpu = new CPU(name);
    }

    @Override
    public void builderDisk(String name) {
        this.disk = new Disk(name);
    }

    @Override
    public void builderKeyboard(String name) {
        this.keyboard = new Keyboard(name);
    }

    @Override
    public void builderMemory(String name) {
        this.memory = new Memory(name);
    }

    @Override
    public void builderMontherboard(String name) {
        this.motherboard = new Motherboard(name);
    }

    @Override
    public void builderScreen(String name) {
        this.screen = new Screen(name);
    }

    public Computer finished() {
        return new Computer(this.cpu, this.disk, this.keyboard, this.memory, this.motherboard, this.screen);
    }
}

产品的各组件

/**
 * 处理器
 *
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class CPU {
    private String name;

    public CPU(String name) {
        this.name = name;
    }
}
/**
 * 磁盘
 *
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class Disk {
    private String name;

    public Disk(String name) {
        this.name = name;
    }
}
/**
 * 键盘
 *
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class Keyboard {
    private String name;

    public Keyboard(String name) {
        this.name = name;
    }
}
/**
 * 内存
 *
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class Memory {
    private String name;

    public Memory(String name) {
        this.name = name;
    }
}
/**
 * 主板
 *
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class Motherboard {
    private String name;

    public Motherboard(String name) {
        this.name = name;
    }
}
/**
 * 屏幕
 *
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class Screen {
    private String name;

    public Screen(String name) {
        this.name = name;
    }
}

具体产品类

/**
 * @author dhj
 * @date 2022/9/13
 */
@ToString
@Setter
public class Computer {
    private final CPU cpu;

    private final Disk disk;

    private final Keyboard keyboard;

    private final Memory memory;

    private final Motherboard motherboard;

    private final Screen screen;

    public Computer(CPU cpu, Disk disk, Keyboard keyboard, Memory memory, Motherboard motherboard, Screen screen) {
        this.cpu = cpu;
        this.disk = disk;
        this.keyboard = keyboard;
        this.memory = memory;
        this.motherboard = motherboard;
        this.screen = screen;
    }
}

主管(协调者)类

package example.mode.creator.builders;

/**
 * @author dhj
 * @date 2022/9/13
 */
public class Director {

    /**
     * 幻16的构建步骤
     *
     * @param builder Rog电脑建造者
     */
    public void magical16Builder(Builders builder) {
        builder.builderCPU("Intel_12_i7");
        builder.builderKeyboard("rog灯效键盘");
        builder.builderDisk("三星DDR5");
        builder.builderMemory("镁光32G");
        builder.builderScreen("2K星云屏");
        builder.builderMontherboard("华硕");
    }

    /**
     * 幻14的构建步骤
     *
     * @param builder Rog电脑建造者
     */
    public void magical14Builder(Builders builder) {
        builder.builderCPU("AMD_R7_6800H");
        builder.builderKeyboard("rog灯效键盘");
        builder.builderDisk("三星DDR4");
        builder.builderMemory("镁光16G");
        builder.builderScreen("1080P");
        builder.builderMontherboard("华硕");
    }
}

客户端

/**
 * @author dhj
 * @date 2022/9/13
 */
public class Client {
    public static void main(String[] args) {
        // 主管实例
        Director director = new Director();
        // 华硕电脑建造者实例
        Builders rogBuilder = new RogBuilder();

        // 通过主管实例,协调建造者执行 magical16 的构建步骤
        director.magical16Builder(rogBuilder);
        Computer magical16 = rogBuilder.finished();
        System.out.println(magical16);

        // 通过主管实例,协调建造者执行 magical14 的构建步骤
        director.magical14Builder(rogBuilder);
        Computer magical14 = rogBuilder.finished();
        System.out.println(magical14);
    }
}

以上代码,整个建造者模式的核心在于Builders Director RogBuilder,首先,Builders中定义了某一类产品的各个抽象建造步骤,RogBuilder实现了这些具体的建造步骤,但目前这些步骤应该怎样调用,缺乏一个管理者,这也是Director的主要作用,在Director中,定义了magical16Builder(Builders builder)magical14Builder(Builders builder)这两个方法,定义了两种不同产品建造时的步骤

注意,magical16Builder()magical14Builder()两个方法的参数中传入的是Builders类型,这也说明了Director只是起一个协调作用,具体的建造逻辑依然由Builders的实现类来执行

上面的例子是一个完整的建造者模式,包含了建造者模式的各项角色,在开发过程中,还有一种比较常用的简化版建造模式的应用;当一个类中的构造参数过多时,可能在调用构造方法时需要传入很多参数,影响代码的可读性且不易于维护,此时可以使用建造模式重构类结构,如下代码示例

以一个用户对象举例,其中有很多属性,可以看到最终的构造方法比较冗长

/**
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class User {
    private String name;
    private Integer age;
    private LocalDateTime birth;
    private List<String> hobbys;
    private String IDCard;
    private String address;

    public User(String name, Integer age, LocalDateTime birth, List<String> hobbys, String IDCard, String address) {
        this.name = name;
        this.age = age;
        this.birth = birth;
        this.hobbys = hobbys;
        this.IDCard = IDCard;
        this.address = address;
    }
}

使用建造模式重构后的代码如下,通过一个内部的建造者类,简化了User的创建形式

import lombok.Data;
import lombok.ToString;

import java.time.LocalDateTime;
import java.util.List;

/**
 * @author dhj
 * @date 2022/9/13
 */
@Data
@ToString
public class User {
    private String name;
    private Integer age;
    private LocalDateTime birth;
    private List<String> hobbys;
    private String IDCard;
    private String address;

    public User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.birth = builder.birth;
        this.hobbys = builder.hobbys;
        this.IDCard = builder.IDCard;
        this.address = builder.address;
    }

    public static final Builder BUILDER = new Builder();

    public static final class Builder {
        private String name;
        private Integer age;
        private LocalDateTime birth;
        private List<String> hobbys;
        private String IDCard;
        private String address;

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

        public Builder age(Integer age) {
            this.age = age;
            return this;
        }

        public Builder birth(LocalDateTime birth) {
            this.birth = birth;
            return this;
        }

        public Builder hobbys(List<String> hobbys) {
            this.hobbys = hobbys;
            return this;
        }

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

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

        public User build() {
            return new User(this);
        }
    }
}

测试类

/**
 * @author dhj
 * @date 2022/9/13
 */
public class MainDemo {
    public static void main(String[] args) {
        User user = User.BUILDER
                .name("zhangsan")
                .birth(LocalDateTime.now())
                .IDCard(RandomUtil.randomNumbers(18))
                .address("地球村-中国路-成都巷-21号")
                .age(19)
                .hobbys(Arrays.asList("唱", "跳", "rap", "篮球"))
                .build();