【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)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容