首页 > 技术文章 > 我要打十个!详解建造者模式(builder pattern)

happyone 2020-03-17 20:23 原文

前言

“我要打十个”,其实是我要打十个野怪!

这十个野怪呢,它们有不同的技能、装备和武器,长得也不一样。这里野怪是一个蛮复杂的对象,由各个不同的部分组成(技能、装备、武器等),不同的野怪的它们各部分之间的构成方式就不同。因此,要创建这种复杂对象,就需要使用建造者模式。

什么是建造者模式

首先建造者模式Gof 23种设计模式之一。也叫Builder模式。

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

我们来品一品这句话,首先是复杂对象,这个复杂对象中可能包含了多个不同的其他对象。其次是这个复杂对象的创建一定是用到了这些其他对象,通过一定的算法组合能才创建出这个对象。最后就是它能通过builder创建出一些特性不同但相似的对象。

好了,借用Linus 名言:

Talk is cheap. Show me the code!!!

表情包图

代码实现

开始创建我们的野怪类,就叫做Hero吧,它的组成部分有技能类Skill,装备类Armor 和武器类Weapon 。

创建Skill、Armor和Weapon类

public class Skill {
    private String skillName;

    public Skill(String skillName) {
        this.skillName = skillName;
    }

    @Override
    public String toString() {
        return "Skill{" +
                "skillName='" + skillName + '\'' +
                '}';
    }
}

public class Armor {
    private String armorName;

    public Armor(String armorName) {
        this.armorName = armorName;
    }

    @Override
    public String toString() {
        return "Armor{" +
                "armorName='" + armorName + '\'' +
                '}';
    }
}

public class Weapon {
    private String weaponName;

    public Weapon(String weaponName) {
        this.weaponName = weaponName;
    }

    @Override
    public String toString() {
        return "Weapon{" +
                "weaponName='" + weaponName + '\'' +
                '}';
    }
}

创建Hero类,在该类中,我们使用静态内部类的方式构建了Builder类,就是我们使用Builder类帮助我们创建对象。

忘了啥是内部类的,可以移驾下面这篇复习下。

Java内部类超详细总结(含代码示例)

public class Hero {
    private final String name;
    private final Skill skill;
    private final Armor armor;
    private final Weapon weapon;

    private Hero(Builder builder){
        this.name = builder.name;
        this.skill = builder.skill;
        this.armor = builder.armor;
        this.weapon = builder.weapon;
    }

    @Override
    public String toString() {
        return "Hero{" +
                "name='" + name + '\'' +
                ", skill=" + skill +
                ", armor=" + armor +
                ", weapon=" + weapon +
                '}';
    }

    public static class Builder{
        private final String name;
        private Skill skill;
        private Armor armor;
        private Weapon weapon;

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

        public Builder withSkill(Skill skill){
            this.skill = skill;
            return this;
        }

        public Builder withArmor(Armor armor){
            this.armor = armor;
            return this;
        }

        public Builder withWeapon(Weapon weapon){
            this.weapon = weapon;
            return this;
        }

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

好了,我们的builder模式的核心代码已经晚了,其实关键的就是Builder类,我们创建复杂对象就是通过Builder类封装了创建的细节,同时,Builder提供了一些公共方法,可以定制这些复杂对象的创建过程。

新建个测试类AppMain,测试一把。

public class AppMain {
    public static void main(String[] args) {
        Hero hero = new Hero.Builder("纳什男爵")
                .withSkill(new Skill("飞龙在天"))
                .withArmor(new Armor("亢龙铠甲"))
                .withWeapon(new Weapon("唾沫星子"))
                .build();

        System.out.println(hero);
    }
}

结果如下:

Hero{name='纳什男爵', skill=Skill{skillName='飞龙在天'}, armor=Armor{armorName='亢龙铠甲'}, weapon=Weapon{weaponName='唾沫星子'}}

当然了,这里也可以创建个“四鸟”,“河蟹”之类的。总之,你要打十个,么有问题啊,我们给你builder十个就好了,而且是不重样的。

在这里插入图片描述

Builder模式在源码中的应用

StringBuilder

其实我们熟知的StringBuilder就是builder模式的典型实现。我们平时使用基本都是这样:

        StringBuilder sb = new StringBuilder();
        sb.append(123).append('a')
                .append(1.23)
                .append(true)
                .append("hhhh");

看着就很平常,soeasy的感觉,其实可以看到它能添加不同的数据类型进去,对应建造者模式中的各个部分,通过append方法的不同组合构建出了不同的StringBuilder对象。

看下源码:

    ......
    
    @Override
    public StringBuilder append(boolean b) {
        super.append(b);
        return this;
    }

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

上面列举了两个重载方法,可以看到其实是调用了父类的重载方法,父类是AbstractStringBuilder

	// 这里只列举这一个父类的方法
    public AbstractStringBuilder append(boolean b) {
        if (b) {
            ensureCapacityInternal(count + 4);
            value[count++] = 't';
            value[count++] = 'r';
            value[count++] = 'u';
            value[count++] = 'e';
        } else {
            ensureCapacityInternal(count + 5);
            value[count++] = 'f';
            value[count++] = 'a';
            value[count++] = 'l';
            value[count++] = 's';
            value[count++] = 'e';
        }
        return this;
    }

Mybatis中的builder模式

Mybatis中的SqlSessionFactoryBuilder、XMLMapperBuilder、XMLStatementBuilder等都使用了builder模式。

这里简单看下SqlSessionFactoryBuilder

在这里插入图片描述

所有的build重载方法都在构建SqlSessionFactory 对象。只是可以根据需要调用不同的方法,传入不同的参数,就可以构建出特性不同的SqlSessionFactory 。

看下其中一个build方法的源码

    ......
    
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                reader.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }
    ......

而且可以看到,这个方法中又使用了一个XMLConfigBuilder 。

Builder模式的使用场景

下面再总结一下builder模式的使用场景吧。

  • 创建复杂对象的算法应该独立于组成对象的部件及其组装方式。
  • 构造对象的过程允许所构造的对象的不同表示。

设计模式往期回顾

Java面试必备:手写单例模式

工厂模式超详解(代码示例)

设计模式之原型模式

设计模式之代理模式

设计模式之委派模式,大名鼎鼎的Spring都在用

公众号:二营长的笔记
免费领资料:公众号内回复“二营长”

推荐阅读