对象心思过于多变,工厂模式满足不了?用建造者模式啊!

2021-12-07 14:59:53 浏览数 (3)

前言

我们知道,常见的设计模式通常分为三大类:创建型模式、行为型模式、结构型模式。今天我们来聊聊创建型模式中的建造者模式,关于它的使用场景、优缺点、组成部分、实战实例讲解以及在JDK中的运用。

工厂模式我们已经比较熟悉,主要用来将实例化对象提取出来,放到一个类中统一维护和管理,从而达到解耦、提高项目的扩展性和维护性。建造者模式的功能与工厂模式很相似,但侧重点不同。下面我们就来详细了解一下创建者模式。

建造者模式简介

创建者模式(Builder Pattern),也翻译作构建者模式,也有人通俗的称作生成器模式。官方定义是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

用大白话解释就是:将一个复杂的对象分解为多个简单的对象,然后一步一步构建形成产品。它主要是将变化部分与不变部分相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

就好比我们要组装一台电脑,需要 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件,这些部件是不变的,但根据自身经济情况和使用的场景不同,可选择每部分的不同配置。

像这么一个复杂的对象的构建,往往需要多个步骤来进行完成。而且每个步骤采用的硬件配置信息可能有所不同。同时,也并不是每个电脑都需要显示器或鼠标的。在通过代码演示上述场景之前,我们先来了解一下建造者模式通常包含的几个角色及功能。

结构与功能

建造者模式通常由四个角色组成:产品、抽象建造者、具体建造者、指挥者。

每个角色功能如下:

  • 产品角色(Product):要创建的具体产品对象,通常是包含多个组成部件的复杂对象。
  • 抽象建造者(Builder):用于创建产品对象的接口或抽象类,其中包含创建产品各个子部件的抽象方法和返回最终最终对象的方法。
  • 具体建造者(Concrete Builder):实现Builder接口,完成不同复杂产品构建和装配。
  • 指挥者(Director):构建一个使用Builder接口的对象,完成复杂对象的创建。它负责控制产品对象的生产过程,同时隔离了用户与对象的产生过程。在指挥者中不涉及具体产品的信息。

结构图如图所示:

模式的实现

作为开发人员,我们使用笔记本时,往往还会配置外置键盘、鼠标和外置显示器。此时,一整套电脑装备就包含了:笔记本电脑、鼠标、键盘、显示器。当然,根据个人的需求不同,有些人不需要鼠标,有些人不需要外置键盘,有些人不需要外置显示器,这就又构成了不同的产品。

先来定义产品类Computer,也就是开发电脑的整体配置:

代码语言:javascript复制
public class Computer {

    /**
     * 笔记本电脑
     */
    private Laptop laptop;

    /**
     * 鼠标
     */
    private Mouse mouse;

    /**
     * 显示器
     */
    private Screen screen;

    /**
     * 键盘
     */
    private Keyboard keyboard;

    public void show() {
        System.out.println("笔记本配置:"   laptop.getName());
        System.out.println("鼠标配置:"   mouse.getName());
        System.out.println("显示器配置:"   screen.getName());
        System.out.println("键盘配置:"   keyboard.getName());
    }

    // 省略getter/setter方法
}

产品由四个组件构成:笔记本电脑、鼠标、显示器、键盘。

抽象建造者:包含创建产品各个子部件的抽象方法。通过接口声明在所有类型生成器中通用的产品构造步骤。

代码语言:javascript复制
public interface ComputerBuilder {

    /**
     * 构建笔记本电脑
     */
    void constructLaptop();

    /**
     * 构建鼠标
     */
    void constructMouse();

    /**
     * 构建屏幕
     */
    void constructScreen();

    /**
     * 构建键盘
     */
    void constructKeyboard();

    /**
     * 返回最终产品对象
     */
    Computer getResult();
}

抽象建造者提供了每个组件构建的统一方法,至于具体每个组件内部的配置可能会有各种各样的变化,这就有具体的实现者来实现。

具体建造者:实现了抽象建造者接口。在当前业务场景中,我们假设公司给的电脑配置有两类,一类是普通的配置,一类是高端配置。这样就有两个具体的建造者了,用来提供构造过程的不同实现。

普通配置实现:

代码语言:javascript复制
public class CommonComputerBuilder implements ComputerBuilder {

    private Computer computer = new Computer();

    @Override
    public void constructLaptop() {
        Laptop laptop = new Laptop("A","华为笔记本");
        computer.setLaptop(laptop);
    }

    @Override
    public void constructMouse() {
        Mouse mouse = new Mouse("A","无线鼠标");
        computer.setMouse(mouse);
    }

    @Override
    public void constructScreen() {
        Screen screen = new Screen("A","液晶显示器");
        computer.setScreen(screen);
    }

    @Override
    public void constructKeyboard() {
        Keyboard keyboard = new Keyboard("A","普通键盘");
        computer.setKeyboard(keyboard);
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

高级配置实现:

代码语言:javascript复制
public class SupperComputerBuilder implements ComputerBuilder {

    private Computer computer = new Computer();

    @Override
    public void constructLaptop() {
        Laptop laptop = new Laptop("S", "Mac Boor Pro");
        computer.setLaptop(laptop);
    }

    @Override
    public void constructMouse() {
        Mouse mouse = new Mouse("A", "无线鼠标");
        computer.setMouse(mouse);
    }

    @Override
    public void constructScreen() {
        Screen screen = new Screen("S", "液晶曲面屏");
        computer.setScreen(screen);
    }

    @Override
    public void constructKeyboard() {
        Keyboard keyboard = new Keyboard("S", "机械键盘");
        computer.setKeyboard(keyboard);
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

在此具体建造者中,设置了每个组件的配置信息,这里做了简化,每个组件都只有类型和名称。而实际使用中,可能每个组件对象会包含不同的数据项。

指挥者:调用建造者中的方法完成复杂对象的创建。

代码语言:javascript复制
public class Director {

    private ComputerBuilder builder;

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

    //产品构建与组装方法
    public Computer construct() {
        builder.constructLaptop();
        builder.constructMouse();
        builder.constructScreen();
        builder.constructKeyboard();
        return builder.getResult();
    }
}

指挥者对整个生产过程进行了封装,对客户端进行了隔离,客户端只用获得最终的产品即可,不用关心组装的过程。

客户端调用示例:

代码语言:javascript复制
public class Client {

    public static void main(String[] args) {

        // 普通配置电脑的组装
        ComputerBuilder builder = new CommonComputerBuilder();
        Director director = new Director(builder);
        Computer product = director.construct();
        product.show();

        System.out.println("------------------");

        // 高级配置电脑的组装
        builder = new SupperComputerBuilder();
        director = new Director(builder);
        product = director.construct();
        product.show();

        // 可拓展其他配置的构建中实现

    }
}

执行程序打印结果如下:

代码语言:javascript复制
笔记本配置:华为笔记本
鼠标配置:无线鼠标
显示器配置:液晶显示器
键盘配置:普通键盘
------------------
笔记本配置:Mac Boor Pro
鼠标配置:无线鼠标
显示器配置:液晶曲面屏
键盘配置:机械键盘

针对上述实例,我们可以发现,如果还需要其他配置的电脑,只需要再创建一个Builder实现类即可,而指导类的组装顺序等也可以进行调整。在这种情况下对原有的功能也不会造成影响,符合设计模式中的开闭原则。

应用场景

适用场景其实才是理解设计模式最重要的,只有知道了适用于什么场景,才能够准确的使用设计模式。

根据上述实例可以看出,建造者(Builder)模式创建的是复杂对象,适用于面向产品的各个具体部分经常出现变化,但将它们组合在一起的算法却相对稳定的场景。

建造者模式适用于以下场景:

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的,产生的结果又不相同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用;
  • 需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异;
  • 使用生成器构造组合树或其他复杂对象,建造者模式可以分步、延时构造产品;
  • 构造函数中有N个可选参数,那new各种实例比较麻烦,需要重载构造函数多次,而且很多参数都具有默认值。

优点与缺点

通过上面的介绍,大概就可以了解到建造者模式的优缺点了。

优点:

  • 各个具体的建造者相互独立,可以很方便地替换具体建造者或增加新的具体建造者,有利于系统的扩展。
  • 封装性好,构建和表示分离,客户端不必知道产品内部组成的细节,便于控制细节风险。
  • 可以更加精细地控制产品的创建过程,建造者可以对创建过程逐步细化,而不对其他模块产生任何影响。

建造者模式的缺点:

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

建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。

工厂模式VS建造者模式

建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,两者可以结合使用。

建造者模式和工厂模式的区别: (1)建造者模式更加注重方法的调用顺序,工厂模式注重创建对象; (2)创建对象的力度不同,建造者模式创建复杂的对象,有各种复杂的部件组成,工厂模式创建出来的对象都一样; (3)关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还需要知道对象由哪些部件组成; (4)建造者模式根据建造过程中顺序的不一样,最终的对象部件组成也不一样。

JDK中建造者的应用

我们最常用的StringBuilder类中就有建造者模式最直接的应用,看一下核心代码:

代码语言:javascript复制
public AbstractStringBuilder append(String str) {
  if (str == null)
    return appendNull();
  int len = str.length();
  ensureCapacityInternal(count   len);
  str.getChars(0, len, value, count);
  count  = len;
  return this;
}

@Override
public StringBuilder append(char c) {
  super.append(c);
  return this;
}

关于StringBuilder具体的接口继承等功能实现,可以对照上面的说明看一下源码实现。

小结

本篇文章带大家实践了一下建造者模式,详细介绍了它的构成与每部分的功能。关于设计模式,最重要的一点就是理解它们的应用场景,在适合的场景下使用合适的设计模式,同时还可以根据具体环境进行灵活的变通。

0 人点赞