建造者模式

596 阅读8分钟

介绍

建造者模式又叫生成器模式,是一种对象构建模式。Builder模式一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构建流程。该模式将产品本身与产品的创建过程解耦。

在软件开发中,也会存在一些构造非常复杂的对象,这些对象拥有一系列的成员属性,有些是基本数据类型,有些是引用类型,在构建的过程中,用户可能会忘记某些成员属性。为了避免这种场景,建造者返回给用户一个完整的产品对象,而客户端无需关心该对象所包含的属性和组建方式,这就是建造者模式的设计动机。

因为一个复杂的对象有很多的组成部分,如汽车有车轮、方向盘、发动机、还有各种小部件等,如何将这些部件装配成一辆汽车,这个装配过程很漫长,也很复杂,对于这种情况,为了在构建过程中对外部隐藏实现细节,就可以使用Builder模式将部件和组装过程分离,使得构建过程和部件都可以自由扩展,两者之间的耦合性也降到最低。

概念

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

建造者模式结构图:

  • Builder:抽象建造者。声明创建一个Product对象的各个部件指定的抽象接口。
  • ConcreteBuilder:具体建造者。实现Builder抽象接口,构建和装配各个部件。
  • Product:产品角色。一个具体的产品对象。
  • Director:指挥者。它主要有两个作用,一是:隔离了客户与对象的生产过程;二是:负责控制产品对象的生产过程。

模式实现

KFC里面一般都有好几种可供客户选择的套餐,它可以根据客户所点的套餐,去做相应的套餐,做好之后来返回给客户一个完整的、美好的套餐。下面我们来模拟这个过程:

套餐类(Product):

public class Meal {
    private String food;
    private String drink;

    ... 省略 setter 和 getter 
}

套餐构造器(Builder):

public abstract class BaseMealBuilder {
    Meal meal = new Meal();

    /**
     * 创建食物
     */
    public abstract void buildFood();

    /**
     * 创建可乐
     */
    public abstract void buildDrink();

    public Meal getMeal() {
        return meal;
    }
}

套餐A、套餐B(ConcreteBuilder),这两个套餐实现抽象套餐类:

public class MealBuilderA extends BaseMealBuilder{

    @Override
    public void buildFood() {
        meal.setFood("一杯可乐");
    }

    @Override
    public void buildDrink() {
        meal.setDrink("一盒薯条");
    }
}
public class MealBuilderB extends BaseMealBuilder{

    @Override
    public void buildFood() {
        meal.setFood("一杯柠檬果汁");
    }

    @Override
    public void buildDrink() {
        meal.setDrink("一个鸡腿");
    }
}

最后是KFC的服务员,它相当于一个指挥者,决定了套餐的实现过程,返回一个美味的套餐:

public class KfcWaiter {
    private BaseMealBuilder mealBuilder;

    public void setMealBuilder(BaseMealBuilder mealBuilder){
        this.mealBuilder = mealBuilder;
    }

    public Meal construct(){
        //准备食物
        mealBuilder.buildFood();
        //准备饮料
        mealBuilder.buildDrink();

        //准备完毕,返回一个完整的套餐给用户
        return mealBuilder.getMeal();
    }
}

开吃:

public class Test {

    public static void main(String[] args) {
        //服务员
        KfcWaiter waiter = new KfcWaiter();
        //服务员准备套餐A
        waiter.setMealBuilder(new MealBuilderA());
        //获得套餐
        Meal meal = waiter.construct();

        System.out.println("套餐A组成部分:" + meal.getFood() + ", " + meal.getDrink());
    }
}

UML类图:

优点

  • 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,能够更加精确的控制复杂对象的产生过程。
  • 将产品的创建过程与产品本身分离开来,可以使用相同的创建过程来得到不同的产品。也就是说细节依赖抽象,符合依赖倒置原则。
  • 每一个具体建造者都相互独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者。用户使用不同的具体建造者即可得到不同的产品对象。

缺点

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

适用场景

  • 需要生成的产品对象有复杂的内部结构,这么产品对象通常包含多个成员属性。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

建造者模式VS工厂模式

  • 工厂模式不需要关心产品构建过程,只关心什么产品由什么工厂生产,同一工厂创建的产品都是一个模样。
  • 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产品一个新产品。此模式创建的出来的是一个复合产品,由各个复杂的部件组成,部件不同所构成的产品也不同。
  • 工厂模式注重只要把这个对象创建出来就OK了「不关心这个产品的组成部分」,而建造者模式不仅要创造出这个产品,还有知道这个产品的组成部分。

总结

建造者模式是将一个复杂对象的创建过程给封装起来,客户只需要知道可以利用对象名或者类型就能够得到一个完整的对象实例,而不需要关心对象的具体创建过程。

建造者模式将对象的创建过程与对象本身隔离开了,使得细节依赖抽象,符合依赖倒置原则。可以使用相同的创建过程来创建不同的产品对象。

建造者模式变形之链式调用

在日常开发中,我们更多的是使用建造者的变形模式。与标准的建造者模式相比,此模式不需要指挥者,也可以不需要抽象建造者,但是能更精确地控制不同配件组合出来的不同的复合产品「不需要创建出相应的具体建造者」。

模式实现

KFC店的生意日益火爆,店里的套餐做不到满足所有客户的口味,所有店里新增加了一项业务,满足客人自定义套餐。这一项业务取得了不错的效果,随着客户的越来越多,服务员明显不够用了,索性就取消服务员吧,让客户自己来选择不同的可乐、鸡腿、柠檬果汁、薯条、豆浆、油条...

Meal类:

public class Meal {
    private final List<String> drink;
    private final List<String> food;

    public Meal(List<String> drink, List<String> food) {
        this.food = food;
        this.drink = drink;
    }

   //省略getter方法
}

MealBuilder类:

public class MealBuilder {
    private final List<String> drinks = new ArrayList<>();
    private final List<String> foods = new ArrayList<>();

    public MealBuilder addDrink(String drink) {
        drinks.add(drink);
        return this;
    }

    public MealBuilder addFood(String food) {
        foods.add(food);
        return this;
    }

    public Meal build() {
        return new Meal(drinks, foods); //构建细节
    }
}

Test类:

public class Test {

    public static void main(String[] args) {
        Meal customMeal = new MealBuilder()
                .addDrink("可乐")
                .addDrink("柠檬果汁")
                .addFood("鸡腿")
                .build();

        System.out.println("自定义套餐:" + customMeal.getDrink() + ", " + customMeal.getFood());
    }
}

UML类图:

优点

  • 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部具体的构建细节。
  • 可以更加精细的控制产品的创建流程,而不需要定义很多具体建造者来实现这种变化。

缺点

  • 需要知道不同的配件能组成什么样的产品。

源码解析(JDK 13; gson 2.8.6)

StringBuffer与StringBuilder我们经常使用,其中StringBuffer非线程安全,StringBuilder线程安全。我们这里以使用StringBuffer为例:

String str = new StringBuilder()
                .append("hi")
                .insert(1, "do")
                .append("world")
                .replace(0, 4, "hello, ")
                .toString();
// 输出结果:hello, world

来看一下UML类图:

大家可能很好奇,AbstractStringBuilder这个类不应该是抽象建造者吗?

其实不是,AbstractStringBuilder已经实现了抽象建造者Appendable类的全部接口方法,在这个类里只有一个抽象方法(toString()方法),这个方法就是用来生产具体产品的。AbstractStringBuilder类还有一个重要的作用,就是把StringBuffer和StringBuilder的公共属性和操作(方法)提取出来,起到代码复用的目的。

Gson库里的GsonBuilder的实现就非常标准了:

Map<String, String> userMap = new HashMap<>();
userMap.put("name", "joke");
userMap.put("age", "20");
userMap.put("gender", "男");
userMap.put("address", "加利福尼亚");
        
Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .serializeNulls()
                .create();

System.out.println(gson.toJson(userMap));
/** 输出结果
    * {
    *   "address": "加利福尼亚",
    *   "gender": "男",
    *   "name": "joke",
    *   "age": "20"
    * }
    */

GsonBuilder类:

//定义在Gson类:static final boolean DEFAULT_SERIALIZE_NULLS = false
private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS; 
//定义在Gson类:static final boolean DEFAULT_PRETTY_PRINT = false
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;

public GsonBuilder serializeNulls() {
    this.serializeNulls = true;
    return this;
}
public GsonBuilder setPrettyPrinting() {
    prettyPrinting = true;
    return this;
}

public Gson create() {
    //省略一些代码...
    //未设置的参数给默认值
    return new Gson(excluder, fieldNamingPolicy, instanceCreators,
        serializeNulls, complexMapKeySerialization,
        generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
        serializeSpecialFloatingPointValues, longSerializationPolicy,
        datePattern, dateStyle, timeStyle,
        this.factories, this.hierarchyFactories, factories);
  }

UML类图:

其实我们更多的情况下是使用这种模式:只需要具体建造者和具体产品这两个类,客户端来采用链式调用的方式进行组合不同的配件来得到不同组合的复合产品。