【Java设计模式三】建造者模式

定义

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

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

类型

创建型

解释

现实世界的例子

Imagine a character generator for a role playing game. The easiest option is to let computer create the character for you. But if you want to select the character details like profession, gender, hair color etc. the character generation becomes a step-by-step process that completes when all the selections are ready.

想象一下角色扮演游戏的角色生成器。最简单的选择是让计算机为您创建角色。但是如果你想选择职业,性别,头发颜色等角色细节,那么角色生成将成为一个循序渐进的过程,在所有选择准备就绪时完成。

简单来说

Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object.

允许您创建不同风格的对象,同时避免构造函数污染(注:构造函数重载过多)。当有多种风格的对象或在创建对象时涉及很多步骤时很有用。

维基百科上说

The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern.

构建器模式是对象创建软件设计模式,其目的是找到伸缩构造器反模式的解决方案。

说到这里,让我补充一下伸缩构造函数反模式是什么。在这一点或其他方面,我们可能都见过类似下面这样的构造函数

public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) {
}

正如您所看到的,构造函数参数的数量很快就会失控,并且可能很难理解参数的排列。此外,如果您希望将来添加更多选项,此参数列表可能会继续增长。这被称为伸缩构造器反模式。

代码示例

v1版本

Director、Builder和Product形成的建造者模式

维基百科Builder Pattern上的例子就是这种,这种建造者模式具有以下几种角色:

角色 类别 职责说明
Product 普通类 具体的产品「即被建造的对象」
Builder 接口或抽象类 抽象的建造者,非必需
ConcreateBuilder 具体的建造者 可以有多个「因为每个建造风格可能不一样」
Director 导演也叫指挥者 统一指挥建造者去建造目标,非必需
Builder Pattern UML(来源于网络)

现实生活中具体例子如:

  • 组装电脑 Product--电脑 Builder--电脑城装机员 Director--电脑城店铺老板
  • 盖房子 Product--房子 Builder--包工头 ConcreateBuilder--工人 Director--建筑设计师
  • 软件开发 Product--软件 Builder--技术主管 ConcreateBuilder--搬砖程序员 Director--产品经理

Builder负责Product类对象的具体过程构建,Director负责指导Build,要求Builder按照其指定的顺序去完成Produt的建造,最后通过Builder返回建造后的结果。网上有人对此种模式进行了形象的类比。

cn.fulgens.design.patterns.creational.builder.v1.Computer

package cn.fulgens.design.patterns.creational.builder.v1;

/**
 * Represents the product created by the builder.
 *
 * @author fulgens
 */
public class Computer {

    // 主板
    private String motherboard;

    // cpu
    private String cpu;

    // 显卡
    private String graphicsCard;

    // 内存条
    private String memoryChip;

    // 硬盘
    private String hardDisk;

    // 电源
    private String powerSupplier;

    // 显示器
    private String monitor;

    // 键盘
    private String keyboard;

    // 鼠标
    private String mouse;

    // setters and getters...

    @Override
    public String toString() {
        return "Computer{" +
                "motherboard='" + motherboard + '\'' +
                ", cpu='" + cpu + '\'' +
                ", graphicsCard='" + graphicsCard + '\'' +
                ", monitor='" + monitor + '\'' +
                ", memoryChip='" + memoryChip + '\'' +
                ", hardDisk='" + hardDisk + '\'' +
                ", powerSupplier='" + powerSupplier + '\'' +
                ", keyboard='" + keyboard + '\'' +
                ", mouse='" + mouse + '\'' +
                '}';
    }
}

cn.fulgens.design.patterns.creational.builder.v1.ComputerBuilder

package cn.fulgens.design.patterns.creational.builder.v1;

/**
 * The builder abstraction.
 *
 * @author fulgens
 */
public interface ComputerBuilder {

    void buildMotherboard(String motherboard);

    void buildCpu(String cpu);

    void buildGraphicsCard(String graphicsCard);

    void buildMemoryChip(String memoryChip);

    void buildHardDisk(String hardDisk);

    void buildPowerSupplier(String powerSupplier);

    void buildMonitor(String monitor);

    void buildKeyboard(String keyboard);

    void buildMouse(String mouse);

    Computer build();

}

cn.fulgens.design.patterns.creational.builder.v1.ComputerBuilderImpl

package cn.fulgens.design.patterns.creational.builder.v1;

/**
 * {@link ComputerBuilder} interface implementation.
 *
 * @author fulgens
 */
public class ComputerBuilderImpl implements ComputerBuilder {

    private Computer computer = new Computer();

    @Override
    public void buildMotherboard(String motherboard) {
        computer.setMotherboard(motherboard);
    }

    @Override
    public void buildCpu(String cpu) {
        computer.setCpu(cpu);
    }

    @Override
    public void buildGraphicsCard(String graphicsCard) {
        computer.setGraphicsCard(graphicsCard);
    }

    @Override
    public void buildMemoryChip(String memoryChip) {
        computer.setMemoryChip(memoryChip);
    }

    @Override
    public void buildHardDisk(String hardDisk) {
        computer.setHardDisk(hardDisk);
    }

    @Override
    public void buildPowerSupplier(String powerSupplier) {
        computer.setPowerSupplier(powerSupplier);
    }

    @Override
    public void buildMonitor(String monitor) {
        computer.setMonitor(monitor);
    }

    @Override
    public void buildKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
    }

    @Override
    public void buildMouse(String mouse) {
        computer.setMouse(mouse);
    }

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

cn.fulgens.design.patterns.creational.builder.v1.CarDirector

package cn.fulgens.design.patterns.creational.builder.v1;

/**
 * Director for Computer
 *
 * @author fulgens
 */
public class ComputerDirector {

    private ComputerBuilder computerBuilder;

    public ComputerDirector(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }

    public Computer construct(String motherboard, String cpu, String graphicsCard, String monitor,
                              String memoryChip, String hardDisk, String powerSupplier, String keyboard,
                              String mouse) {
        computerBuilder.buildMotherboard(motherboard);
        computerBuilder.buildCpu(cpu);
        computerBuilder.buildGraphicsCard(graphicsCard);
        computerBuilder.buildMemoryChip(memoryChip);
        computerBuilder.buildHardDisk(hardDisk);
        computerBuilder.buildPowerSupplier(powerSupplier);
        computerBuilder.buildMonitor(monitor);
        computerBuilder.buildKeyboard(keyboard);
        computerBuilder.buildMouse(mouse);
        return computerBuilder.build();
    }
    
    public static void main(String[] args) {
        ComputerBuilder computerBuilder = new ComputerBuilderImpl();
        ComputerDirector computerDirector = new ComputerDirector(computerBuilder);
        Computer computer = computerDirector.construct("Xxx牌B360", "Xxx牌 I5", "Xxx牌GTX1060", "Xxx牌显示器",
                "Xxx牌16G", "Xxx牌SSD", "Xxx牌电源", "Xxx牌键盘", "Xxx牌鼠标");
        System.out.println(computer);
    }
}

运行测试方法:

Computer{motherboard='Xxx牌B360', cpu='Xxx牌 I5', graphicsCard='Xxx牌GTX1060', monitor='Xxx牌显示器', memoryChip='Xxx牌16G', hardDisk='Xxx牌SSD', powerSupplier='Xxx牌电源', keyboard='Xxx牌键盘', mouse='Xxx牌鼠标'}

UML类图

v2版本

通过静态内部类方式实现的fluent api构造:

https://github.com/iluwatar/java-design-patterns/tree/master/builder上的例子就是这种

cn.fulgens.design.patterns.creational.builder.v2.PositionEnum

package cn.fulgens.design.patterns.creational.builder.v2;

/**
 * 英雄分路枚举类
 *
 * @author fulgens
 */
public enum PositionEnum {

    TOP("上路"),
    MIDDLE("中路"),
    BOTTOM("下路"),
    JUNGLE("打野");

    private String title;

    PositionEnum(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return title;
    }
}

cn.fulgens.design.patterns.creational.builder.v2.Position

package cn.fulgens.design.patterns.creational.builder.v2;

import java.util.Set;

/**
 * 英雄分路
 *
 * @author fulgens
 */
public class Position {

    // 推荐分路
    private PositionEnum recommendedPosition;

    // 可选分路
    private Set<PositionEnum> optionalPositions;

    public PositionEnum getRecommendedPosition() {
        return recommendedPosition;
    }

    public void setRecommendedPosition(PositionEnum recommendedPosition) {
        this.recommendedPosition = recommendedPosition;
    }

    public Set<PositionEnum> getOptionalPositions() {
        return optionalPositions;
    }

    public void setOptionalPositions(Set<PositionEnum> optionalPositions) {
        this.optionalPositions = optionalPositions;
    }
    
    @Override
    public String toString() {
        return "Position{" +
                "recommendedPosition=" + recommendedPosition +
                ", optionalPositions=" + optionalPositions +
                '}';
    }
}

cn.fulgens.design.patterns.creational.builder.v2.Profession

package cn.fulgens.design.patterns.creational.builder.v2;

/**
 * 英雄职业枚举类
 *
 * @author fulgens
 */
public enum Profession {

    WARRIOR("战士"),
    MAGE("法师"),
    TANK("坦克"),
    ASSASSIN("刺客"),
    SHOOTER("射手"),
    AUXILIARY("辅助");

    private String title;

    Profession(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return title;
    }
}

cn.fulgens.design.patterns.creational.builder.v2.Skin

package cn.fulgens.design.patterns.creational.builder.v2;

/**
 * 皮肤类
 *
 * @author fulgens
 */
public class Skin {

    private String name;

    private String skinImage;

    public String getName() {
        return name;
    }

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

    public String getSkinImage() {
        return skinImage;
    }

    public void setSkinImage(String skinImage) {
        this.skinImage = skinImage;
    }
    
    @Override
    public String toString() {
        return "Skin{" +
                "name='" + name + '\'' +
                ", skinImage='" + skinImage + '\'' +
                '}';
    }
}

cn.fulgens.design.patterns.creational.builder.v2.Hero

package cn.fulgens.design.patterns.creational.builder.v2;

import java.util.Set;

/**
 * MOBA类游戏Hero
 *
 * @author fulgens
 */
public class Hero {

    // 名称
    private String name;

    // 职业
    private Set<Profession> professions;

    // 位置
    private Position position;

    // 皮肤
    private Set<Skin> skins;

    public Hero(Builder builder) {
        this.name = builder.name;
        this.professions = builder.professions;
        this.position = builder.position;
        this.skins = builder.skins;
    }

    public Set<Profession> getProfessions() {
        return professions;
    }

    public void setProfessions(Set<Profession> professions) {
        this.professions = professions;
    }

    public Position getPosition() {
        return position;
    }

    public void setPosition(Position position) {
        this.position = position;
    }

    public Set<Skin> getSkins() {
        return skins;
    }

    public void setSkins(Set<Skin> skins) {
        this.skins = skins;
    }

    @Override
    public String toString() {
        return "Hero{" +
                "name='" + name + '\'' +
                ", professions=" + professions +
                ", position=" + position +
                ", skins=" + skins +
                '}';
    }

    private static class Builder {

        private String name;

        private Set<Profession> professions;

        private Position position;

        private Set<Skin> skins;

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

        public Builder professions(Set<Profession> professions) {
            this.professions = professions;
            return this;
        }

        public Builder position(Position position) {
            this.position = position;
            return this;
        }

        public Builder skins(Set<Skin> skins) {
            this.skins = skins;
            return this;
        }

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

测试类

cn.fulgens.design.patterns.creational.builder.v2.BuilderTest

package cn.fulgens.design.patterns.creational.builder.v2;

import org.junit.Assert;
import org.junit.Test;

import java.util.HashSet;
import java.util.Set;

public class BuilderTest {

    @Test
    public void testBuildHero() {
        Set<Profession> professions = new HashSet<>();
        professions.add(Profession.MAGE);
        professions.add(Profession.ASSASSIN);
        Position position = new Position();
        position.setRecommendedPosition(PositionEnum.MIDDLE);
        Set<Skin> skins = new HashSet<>();
        Skin classicSkin = new Skin("经典", "http://xxx.com/images/xxx");
        Skin paySkin = new Skin("修竹墨客", "http://xxx.com/images/xxx");
        skins.add(classicSkin);
        skins.add(paySkin);
        Hero.Builder builder = new Hero.Builder();
        Hero hero = builder.name("上官婉儿")
                            .professions(professions)
                            .position(position)
                            .skins(skins)
                            .build();

        System.out.println(hero);
        Assert.assertEquals(PositionEnum.MIDDLE.toString(), hero.getPosition().getRecommendedPosition().toString());
    }

}

运行测试

Hero{name='上官婉儿', professions=[法师, 刺客], position=Position{recommendedPosition=中路, optionalPositions=null}, skins=[Skin{name='经典', skinImage='http://xxx.com/images/xxx'}, Skin{name='修竹墨客', skinImage='http://xxx.com/images/xxx'}]}

UML

Builder Pattern UML

适用场景

  • 一个对象有很复杂的内部结构(很多属性)

  • 想把复杂对象的创建和使用分离

优缺点

优点

  • 封装性好,创建和使用分离

  • 扩展性好、建造类之间独立、一定程度上解耦

缺点

  • 只适用于产品具有相同的特点「过程和步骤」,如果产品之间差异非常大,则不适用「使用范围受限」

  • 产品内部发生变化,建造者就要修改,成本较大

建造者模式 Vs 工厂模式

相似点

都属于创建型模式,均用于创建产品对象

区别

  • 创建对象的粒度不同:

工厂模式创建的对象都是一个鸟样,而建造者模式创建的是一个具有风格的复杂产品,由多个复杂的部件组成,部件不同所构成的产品也不同

  • 关注点不同:

工厂模式注重只要把这个对象创建出来就 行「不关心这个产品的组成部分」,而建造者模式不似要创造出这个产品,还有知道这个产品的组成部分

Real world examples

  • java.lang.StringBuilder

  • java.nio.ByteBuffer as well as similar buffers such as FloatBuffer, IntBuffer and so on.

  • java.lang.StringBuffer

  • All implementations of java.lang.Appendable

  • com.google.common.collect.ImmutableSet (Guava)

  • com.google.common.cache.CacheBuilder (Guava)

  • org.springframework.beans.factory.support.BeanDefinitionBuilder(Spring)

  • org.apache.ibatis.session.SqlSessionFactoryBuilder(Mybatis)

  • org.springframework.boot.builder.SpringApplicationBuilder(Spring Boot)

推荐阅读更多精彩内容