一篇文章就彻底弄懂建造者模式(Builder Pattern)

背景

当一个类的内部数据过于复杂的时候(通常是负责持有数据的类,比如Config、VO、PO、Entity...),要创建的话可能就需要了解这个类的内部结构,还有这些东西是怎么组织装配等一大坨乱七八糟的东西,这个时候就会增加学习成本而且会很混乱,这个时候就想啊想一种什么法子来管理一下这个类中的数据呢,怎么在创建的时候让它按部就班的来,并且代码可读性很好别让我看花了眼啊,我要的东西也能都很好设置进来,这就是Builder模式的应用场景,Builder模式可以将一个类的构建和表示进行分离。

1.介绍

1.1什么是构建者模式

创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使
得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

1.2适用场景:

  • 隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
  • 多个部件都可以装配到一个对象中,但产生的运行结果不相同
  • 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
  • 初始化一个对象时,参数过多,或者很多参数具有默认值
  • Builder模式不适合创建差异性很大的产品类
    产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本
  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;

1.3 主要作用

在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

  • 用户只需要给出指定复杂对象的类型和内容;
  • 建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

1.4 解决的问题

  • 方便用户创建复杂的对象(不需要知道实现过程)
  • 代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)

例子:造汽车 & 买汽车。

  1. 工厂(建造者模式):负责制造汽车(组装过>程和细节在工厂内)
  2. 汽车购买者(用户):你只需要说出你需要的>型号(对象的类型和内容),然后直接购买就可>>以使用了
    (不需要知道汽车是怎么组装的(车轮、车门、>发动机、方向盘等等))

2. 模式原理

2.1 UML类图 & 组成

builder.png

2.2模式讲解:

  • 指挥者(Director)直接和客户(Client)进行需求沟通;
  • 沟通后指挥者将客户创建产品的需求划分为各个部件的建造请求(Builder);
  • 将各个部件的建造请求委派到具体的建造者(ConcreteBuilder);
  • 各个具体建造者负责进行产品部件的构建;
  • 最终构建成具体产品(Product)。

3.用 builder 模式创建共享单车为例子,示例代码:

产品类:


public class Bike { 

    private IFrame frame; 
    private ISeat seat; 
    private ITire tire; 
    
    public IFrame getFrame() { 
        return frame; 
    } 
    public void setFrame(IFrame frame) { 
        this.frame = frame; 
    } 
    public ISeat getSeat() { 
        return seat; 
    } 
    public void setSeat(ISeat seat) { 
        this.seat = seat; 
    } 
    public ITire getTire() { 
        return tire; 
    } 
    public void setTire(ITire tire) { 
        this.tire = tire; 
    } 
} 

Builder 类:

// 抽象 builder 类 
public abstract class Builder { 
    abstract void buildFrame(); 
    abstract void buildSeat(); 
    abstract void buildTire(); 
    abstract Bike createBike(); 
} 

ConcreteBuilder 类 :

// 具体 builder 类 
public class MobikeBuilder extends Builder{ 
    private Bike mBike = new Bike(); 
    @Override 
    void buildFrame() { 
        mBike.setFrame(new AlloyFrame()); 
    } 
    @Override 
    void buildSeat() { 
        mBike.setSeat(new DermisSeat()); 
    } 
    @Override 
    void buildTire() { 
        mBike.setTire(new SolidTire()); 
    } 
    @Override 
    Bike createBike() { 
        return mBike; 
    } 
} 

public class OfoBuilder extends Builder{ 
    private Bike mBike = new Bike(); 
    @Override 
    void buildFrame() { 
        mBike.setFrame(new CarbonFrame()); 
    } 
    @Override 
    void buildSeat() { 
        mBike.setSeat(new RubberSeat()); 
    } 
    @Override 
    void buildTire() { 
        mBike.setTire(new InflateTire()); 
    } 
    @Override 
    Bike createBike() { 
        return mBike; 
    } 
} 

指挥者类:

public class Director { 
    private Builder mBuilder = null; 
    public Director(Builder builder) { 
        mBuilder = builder; 
    } 
    public Bike construct() { 
        mBuilder.buildFrame(); 
        mBuilder.buildSeat(); 
        mBuilder.buildTire(); 
        return mBuilder.createBike(); 
    } 
}

客户端使用:

public class Click { 
    public static void main(String[] args) { 
        showBike(new OfoBuilder()); 
        showBike(new MobikeBuilder()); 
    } 
    private void showBike(Builder builder) {
        Director director = new Director(builder); 
        Bike bike = director.construct(); 
        bike.getFrame().frame(); 
        bike.getSeat().seat(); 
        bike.getTire().tire(); 
    } 
} 

上面示例是 Builder模式的常规用法,导演类 Director 在 Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合,示例代码:

改造后的抽象建造者:

public abstract class NewBuilder { 
    abstract void buildFrame(); 
    abstract void buildSeat(); 
    abstract void buildTire(); 
    abstract Bike createBike(); 
    /** 
    * 把导演类中的construct()方法合并到抽象建造者类中 
    * 
    * @return 具体产品对象 
    */ 
    public Bike construct() { 
        this.buildFrame(); 
        this.buildSeat(); 
        this.buildTire(); 
        return this.createBike(); 
    } 
} 

这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中
除了上面的用途外,还有另外一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用 builder模式进行重构,重构前示例代码:

// 省略 getter 和 setter 方法 
public class Computer { 
    private String cpu; 
    private String screen; 
    private String memory; 
    private String mainboard; 
    public Computer(String cpu, String screen, String memory, String mainboard) { 
        this.cpu = cpu; 
        this.screen = screen; 
        this.memory = memory; 
        this.mainboard = mainboard; 
    } 
} 
public class NewComputer { 
    private String cpu; 
    private String screen; 
    private String memory; 
    private String mainboard; 
    public NewComputer() { 
        throw new RuntimeException(“can’t init”); 
    } 
    private NewComputer(Builder builder) { 
        cpu = builder.cpu; 
        screen = builder.screen; 
        memory = builder.memory; 
        mainboard = builder.mainboard; 
    } 
    public static final class Builder { 
        private String cpu; 
        private String screen; 
        private String memory; 
        private String mainboard; 
        
    public Builder() {} 
    
    public Builder cpu(String val) { 
        cpu = val; 
        return this; 
    } 
    public Builder screen(String val) { 
        screen = val; 
        return this; 
    } 
    public Builder memory(String val) { 
        memory = val; 
        return this; 
    } 
    public Builder mainboard(String val) { 
        mainboard = val; 
        return this; 
    } 
    public NewComputer build() {
        return new  NewComputer(this);} 
    } 
} 

客户端:


public class Click { 
    public static void main(String[] args) { 
        // 非 Builder 模式 
        Computer computer = new Computer(“cpu”, “screen”, “memory”, “mainboard”); 
        // Builder 模式 
        NewComputer newComputer = new NewComputer.Builder() 
        .cpu(“cpu”) 
        .screen(“screen”) 
        .memory(“memory”) 
        .mainboard(“mainboard”) 
        .build(); 
    } 
} 

上面的示例代码只是传入四个参数,如果参数是十四个甚至更多,builder 模式的优势将会更加明显,传递参数更加灵活,代码具有更高的可读性.

4.优缺点比较

一般的套路:优点是比较简单,开发效率高,缺点是如果参数真的很多的话鬼知道每个对应的是什么意思啊。

Builder模式:优点是可以将构造器的setter方法名取成类似注释的方式,这样我们可以很清晰的知道刚才究竟设置的什么值,可读性较高,缺点是比较冗长。

优点:

  • 使用建造者模式可以使客户端不必知道产品内部组成的细节。
  • 具体的建造者类之间是相互独立的,这有利于系统的扩展。
  • 具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。

缺点:

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

5.建造者模式与抽象工厂模式的比较:

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族 。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
  • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车

改文章已同步到公众号,欢迎大家关注!


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