Java设计模式之-建造者模式(Builder)

关于建造者模式(或者又叫构造者模式),我在网上看了很多文章。其中不乏很多人直接把建造者模式等同于builder构造器。我想说这是两种完全不同的层次和方向,一种是从系统设计层面考虑的设计模式,用于使整个系统构件间解耦更加明显,更易于扩展;另一种则是为了让构造器能适应多个参数而出现的构造函数变体。
这里不再对网上漫天的Builder构造器进行解释,只针对建造者模式进行讨论。

建造者模式:将类的使用者与类的建造方式解耦,将类的使用者与类的建造者解耦。

当然,上面这句话也是我个人对建造者模式的理解。建造者模式就是实现了三点之间的解耦:类的使用者、类的建造者、类的建造方式
我们先来看一下建造者模式的示意类图:

建造者模式
  • Builder, 抽象建造者,定义了建造一个类(Product)需要实现的方法;
  • ConcreteBuilder,具体建造者,实现了抽象建造者定义的方法;
  • Product,产品,其实就是建造者需要建造的东西;
  • Director,其实它才是真正的类的建造者,上面所说的Builder,其实只是定义了类的建造方式,但是真正和类的使用者交互的是Director,它作为具体类的提供者,完成了建造的步骤。

OK, 我们现在来从上面说的两个解耦进行后续的解释。


将类的使用者与类的建造方式解耦

平常我们想进行一个类的构造会怎么写?一般来说我们先new一个实例出来,然后对各个变量进行set,最后做我们想做的事情。这里大家也可以换成直接在构造函数里面传入参数,大致意思是一样的。

Foo foo = new Foo();
foo.setA(123);
foo.setB("abc");
foo.setC(new bar());
foo.doSomething();

但是这种做法,实际上将类的初始化和各项配置工作都移交给了类的使用者来做,大大增强了类的使用者和类的建造方式之间的耦合关系,类的使用者必须完全了解如何建造这个类才可以,否则便等不到一个功能完好的实例,更不要说正确地使用了。

OK,那如果说我现在有这样一个产品类,会被别人使用到。我们先假设产品类是这个样子:

public class Product {
    private String name;

    private BigDecimal price;

    public String getName() {
        return name;
    }

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

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

而我们存在多种建造这个产品的方式,但首先我们需要定义一个接口,用以确定产品的建造者需要做哪些工作:

public interface IProductBuilder {
    public void giveName();
    public void givePrice();
    public Product build();
}

而后我们有了两个建造商品的类,一个生产高质量商品,另一个生产普通商品:

public class HighQualityProductBuilder implements IProductBuilder{
    private Product product = new Product();;
    @Override
    public void giveName() {
        product.setName("A High Quality Product comes out!");
    }

    @Override
    public void givePrice() {
        product.setPrice(new BigDecimal("299.99"));
    }

    @Override
    public Product build() {
        giveName();
        givePrice();
        refine();
        return product;
    }

    private void refine(){
        System.out.println("Don't know how, but the product get refined!");
    }
}
public class NormalProductBuilder implements IProductBuilder {
    private Product product = new Product();;

    @Override
    public void giveName() {
        product.setName("Product with normal quality......");
    }

    @Override
    public void givePrice() {
        product.setPrice(new BigDecimal("1.59"));
    }

    @Override
    public Product build() {
        giveName();
        givePrice();
        return product;
    }
}

这个时候我们能够看到,在Builder类中,我已经将值赋给了product的域,并且在build()方法中实现了产品的装配和建造过程。后面会提到,这其实是建造者模式的一大问题所在。

而后,我们在main方法中模拟使用该类,这样其实就能够实现了将类的使用者与类的建造方式解耦这一目的:

    public static void main(String[] args){
        IProductBuilder builder = new HighQualityProductBuilder();
        Product product = builder.build();
    }

但是到此为止,我们做得还不够。大家可以看到,在类图中展示的Builder、ConcreteBuilder、和Product都已经出现了,但是Director还迟迟不肯露面。下面我们就来说一下Director在整个建造者模式中的作用。


将类的使用者与类的建造者解耦

为了实现这一解耦目标,我们引入了Director这个参与者。它主要做的工作,就是封装了Builder,然后直接和外界类的使用者进行交互:

public class Director {
    private IProductBuilder builder;
    public Director(IProductBuilder builder){
        this.builder = builder;
    }
    
    public Product construct(){
        return builder.build();
    }
}

而后我们在main方法中使用director来替代原来直接写builder的方式:

    public static void main(String[] args){
        Director director = new Director(new NormalProductBuilder());
        Product product = director.construct();
    }

看到这里,很多人可能会骂我根本就不懂。这不还是写死了NormalProductBuilder了么?不照样是紧耦合了么?有什么意义?其实如果大家写过一点Spring就能顿悟这里面的妙处,只要我在Director构造时使用注入,并将其实现为一个Bean,就可以很轻松地将其进行解耦。由一个Director来作为选择和管理Builder的入口,而通过注入的方式将使用者与后方隔离开来,这才是建造者模式真正强大的地方。

一大问题

另外我上面也说了,建造者模式的一大问题,就是它把类的建造过程写死了,绑死了,写成了一套流程化的代码逻辑,不易更改。

所以说,建造者模式适用于不易变更的且构建复杂的类的实例化场景,而对那些生成过程经常改变的类,则不建议使用该模式,因为得到的收益可能远比不上修改的成本。

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

推荐阅读更多精彩内容

  • 没有人买车会只买一个轮胎或者方向盘,大家买的都是一辆包含轮胎、方向盘和发动机等多个部件的完整汽车。如何将这些部件组...
    justCode_阅读 1,822评论 1 6
  • 定义 建造模式是对象的创建模式。建造模式可以将一个产品的内部表象(internal representation)...
    步积阅读 6,933评论 1 7
  • 模式定义 建造者模式:将一个复杂产品的创建与表示分离,使得同样的创建过程可以创建不同的表示客户端不用去关心产品对象...
    C_zx阅读 503评论 1 5
  • 我们知道,生成站点地图之后会利于搜索引擎蜘蛛对网站信息的结构化收录,大家都在用插件,插件能少一个就少一个。现在给出...
    Yephy阅读 2,534评论 0 3
  • 我是一个和尚,在诵经房里抄楞严经。梵文的音,难以理解的字,不管理不理解,悟性天资的问题。 那天,一个朋友,...
    楊懿阅读 278评论 0 0