设计模式之建造者模式

建造者模式(Builder Pattern)也叫做生成器模式,今天让我们一起学习一下建造者模式。

一、基本介绍

建造者模式的定义为:将一个复杂对象的构建和它的表示分离开,使得同样的构建过程可以创建不同的表示。

建造者模式主要由4个角色来组成:

1 . 抽象建造者(Builder)角色:该角色用于规范产品的各个组成部分,并进行抽象,一般独立于应用程序的逻辑。

2 . 具体建造者(Concrete Builder)角色:该角色实现抽象建造者中定义的所有方法,并且返回一个组建好的产品实例。

3 . 产品(Product)角色:该角色是建造者中的复杂对象,一个系统中会有多于一个的产品类,这些产品类并不一定有共同的接口,完全可以是不相关联的。

4 . 导演者(Director)角色:该角色负责安排已有模块的顺序,然后告诉Builder开始建造。

二、代码实现建造者模式

上面说的东西都只是理论,多少有些空洞,现在我们就通过一个简单的例子来体验一下建造者模式。

1 . 创建产品类Product.java

/**
 * 产品类
 *
 */
public class Product {
    //业务处理方法
}

由于我们的目的是最终生产产品,所以产品类中的逻辑实现我们暂且不关注,具体的方法要根据业务来写。

2 . 创建抽象建造者类Builder.java

/**
 * 抽象建造者类
 *
 */
public abstract class Builder {
    //设置产品的不同部分,以获得不同的产品
    public abstract void setPart1();
    public abstract void setPart2();
    public abstract void setPart3();

    //建造产品
    public abstract Product builderProduct();
}

该类是一个抽象类,其中我们声明了4个抽象方法,前面三个是负责给产品添加不同的部件,第四个方法是负责建造产品。但这只是一个框架,还没有具体的实现。

3 . 创建具体建造者类ConcreteBuilder.java

/**
 *具体建造者类
 *
 */
public class ConcreteBuilder extends Builder{
    //一个产品
    private Product product = new Product();

    //开始安装产品的部件
    @Override
    public void setPart1() {
        //为product安装部件1
    }

    @Override
    public void setPart2() {
        //为product安装部件2
    }

    @Override
    public void setPart3() {
        //为product安装部件3
    }

    //建造一个产品
    @Override
    public Product builderProduct() {
        // TODO Auto-generated method stub
        return product;
    }
}

该类会继承自抽象建造者类Builder,并实现其中的方法。开始先声明一个产品,然后在各个setPart3方法中添加具体的逻辑,然后在builderProduct()方法中返回生产好的产品。

4 . 创建导演类Director()

上面我们已经实现了具体的建造者类,也具体写好了安装每个部件的方法,最后一步就是由导演类来知道具体构建者类如何制造产品啦。制造完的产品会交给导演类负责处理。

/**
 * 导演类
 *
 */
public class Director1 {
    private Builder builder = new ConcreteBuilder();

    //构建产品,调用各个添加部件的方法
    public Product build(){
        builder.setPart1();
        builder.setPart2();
        builder.setPart3();
        //调用构建产品的方法来构建产品
        return builder.builderProduct();
    }
}

这个类很简单,其实就是获得一个具体的建造者对象,然后调用具体的方法给产品安装部件,安装完成后调用builder.builderProduct()方法获得建造好的产品,此处导演类是在build()方法中完成了建造过程,同时将获得的建造好的产品返回出去,以供其他模块使用该产品。

此处的导演类起到了封装左右,可以避免高层模块深入到建造者内部的实现类,而且导演类可以有多个,根据业务逻辑分别用来建造不同的产品并输出。

三、建造者模式的优点

建造者模式的有点主要有以下几点:

1 . 封装性。使用建造者模式可以使客户端不必知道产品的内部实现细节

2 . 独立易扩展。由于建造过程是独立的,更利于后期扩展

3 . 便于控制细节风险。由于具体的产品建造是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响

四、建造者模式的使用场景

说了这么多建造者模式的好处,那我们应该在什么场合使用它们呢,看下面:

1 . 相同的方法,不同的执行顺序,产生不同的结果。这种情况我们只要利用不同的导演类来控制建造过程,就可以产生不同的产品,其他部分不用修改

2 . 多个零件和部件,都可以装配到一个对象中,装配不同的零件,产生不同的运行结果,我们同样可以通过修改导演类来产生不同的产品

3 . 产品类非常复杂,此时我们也可以将产品的建造方法和具体的建造顺序分离开来处理

4 . 在对象创建过程中会用到系统的一些其他对象,这些对象在产品对象的创建过程中不容易得到,可以采用建造者模式封装该对象的创建过程

注:建造者模式关注的是零件的类型和装配工艺的顺序

五、建造者模式实站

说了半天建造产品,没行动有卵用,来来来,咋们就用刚学的建造者模式生产两台不同类型的电脑练练手,代码敲起来

1 . 创建产品父类Computer

该类是我们建造的计算机的父类,其中包含了计算机的公共属性以及属性的get和set方法

package cn.codekong.start;

/**
 * 计算机类
 */
public class Computer {
    //型号
    private String type;
    //CPU
    private String cpu;
    //内存
    private String ram;
    //硬盘
    private String hardDisk;
    //显示器
    private String monitor;
    //操作系统
    private String os;

    //对应的get和set方法
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getCpu() {
        return cpu;
    }
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
    public String getRam() {
        return ram;
    }
    public void setRam(String ram) {
        this.ram = ram;
    }
    public String getHardDisk() {
        return hardDisk;
    }
    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }
    public String getMonitor() {
        return monitor;
    }
    public void setMonitor(String monitor) {
        this.monitor = monitor;
    }
    public String getOs() {
        return os;
    }
    public void setOs(String os) {
        this.os = os;
    }
}

2 . 创建具体的产品类T410类和X201

这两个类均继承自上面的Computer类,并且我们在该类在添加了两台计算机特有的属性,T410计算机用于独立显卡,而X201没有,同时重写了它们的toString()方法,返回它们的参数,便于最后我们建造完计算机后输出它们各自的配置参数.

T410.java
package cn.codekong.start;

public class T410 extends Computer{
    //显卡
    private String graphicCard;
    public T410() {
        this.setType("Thinkpad T410");
    }

    public String getGraphicCard(){
        return graphicCard;
    }

    public void setGraphicCard(String graphicCard){
        this.graphicCard = graphicCard;
    }

    @Override
    public String toString() {
        return "型号:\t" + this.getType() + "\nCPU\t" + this.getCpu()
                + "\n内存\t" + this.getRam() + "\n硬盘\t" + this.getHardDisk()
                + "\n显卡\t" + this.getGraphicCard() + "\n显示器\t" + this.getMonitor()
                + "\n操作系统\t" + this.getOs();
    }
}
X201.java
package cn.codekong.start;

public class X201 extends Computer{
    public X201() {
        this.setType("Thinkpad X201");
    }
    @Override
    public String toString() {
        return "型号:\t" + this.getType() + "\nCPU\t" + this.getCpu()
                + "\n内存\t" + this.getRam() + "\n硬盘\t" + this.getHardDisk()
                + "\n显示器\t" + this.getMonitor() + "\n操作系统\t" + this.getOs();
    }
}

上面的(1)(2)步只是相当于我们有了我们需要的产品类已经声明好了,下面开始写我们的抽象建造类

3 . 抽象计算机建造类ComputerBuilder

我们创建了一个接口,在其中声明了我们要建造产品过程中需要用到的方法,其实就是产品的各个建造步骤

package cn.codekong.start;
/**
 * 抽象的计算机建造者
 * 声明建造的公共方法
 */
public interface ComputerBuilder {
    //建造CPU
    void buildCpu();
    //建造内存
    void buildRam();
    //建造硬盘
    void buildHardDisk();
    //建造显卡
    void buildGraphicCard();
    //建造显示器
    void buildMonitor();
    //建造操作系统
    void buildOs();

    //得到建造好的计算机
    Computer getResult();
}

4 . 创建具体的建造类T410Builder类和X201Builder

这两个类要实现上一步定义的接口,然后实现里面的各个方法,其实就是实现各个具体的组装方法中的逻辑,但此时只是把每一个组装的步骤的逻辑具体化了,还没有开始正式组装。

T410Builder.java
package cn.codekong.start;

/**
 * T410的具体建造者实现抽象的计算机建造者
 */
public class T410Builder implements ComputerBuilder{
    private T410 computer = new T410();
    @Override
    public void buildCpu() {
        computer.setCpu("i5-450");
    }

    @Override
    public void buildRam() {
        computer.setRam("4G 1333MHz");
    }

    @Override
    public void buildHardDisk() {
        computer.setHardDisk("500G 7200转");
    }

    @Override
    public void buildGraphicCard() {
        computer.setGraphicCard("Nvidia");
    }

    @Override
    public void buildMonitor() {
        computer.setMonitor("14英寸 1280*800");
    }

    @Override
    public void buildOs() {
        computer.setOs("Windows7 旗舰版");
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}
X201Builder.java
package cn.codekong.start;

/**
 * X201计算机的具体建造类实现抽象的计算机建造类
 */
public class X201Builder implements ComputerBuilder{
    private X201 computer = new X201();
    @Override
    public void buildCpu() {
        computer.setCpu("i3-350");
    }

    @Override
    public void buildRam() {
        computer.setRam("2G 1333M");
    }

    @Override
    public void buildHardDisk() {
        computer.setHardDisk("250G 5400 转");
    }

    @Override
    public void buildGraphicCard() {
        //无独立显卡
    }

    @Override
    public void buildMonitor() {
        computer.setMonitor("12英寸 1280*800");
    }

    @Override
    public void buildOs() {
        computer.setOs("Windows7 Home版");
    }

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

5 . 导演类知道具体的建造者类建造产品ComputerDirector

该类就比较简单了,在该类内部实现两个构造方法,分别对应实现两种计算机的过程,这时候才是正式的建造过程。

package cn.codekong.start;

/**
 * 计算机导演类,知道具体建造者建造计算机
 */
public class ComputerDirector {
    ComputerBuilder builder;

    //建造T410计算机
    public T410 constructT410(){
        builder = new T410Builder();
        builder.buildCpu();
        builder.buildRam();
        builder.buildHardDisk();
        builder.buildGraphicCard();
        builder.buildMonitor();
        builder.buildOs();
        //建造结束将产品返回供外部使用
        return (T410)builder.getResult();
    }

    //建造X201计算机
    public X201 constructX201(){
        builder = new X201Builder();
        builder.buildCpu();
        builder.buildRam();
        builder.buildHardDisk();
        //由于X201没有独立显卡,则不调用buildGraphicCard()函数
        //builder.buildGraphicCard();

        builder.buildMonitor();
        builder.buildOs();
        //建造结束将产品返回供外部使用
        return (X201)builder.getResult();
    }
}

6 . 最后让我们测试一下建造的产品是否是好的,新建测试类ComputerTest

package cn.codekong.start;

/**
 * 计算机建造测试类
 */
public class ComputerTest {
    public static void main(String[] args) {
        ComputerDirector computerDirector = new ComputerDirector();
        //建造T410计算机
        Computer t410 = computerDirector.constructT410();
        //输出T410计算机的配置参数
        System.out.println(t410);

        System.out.println("------------我是分割线----------------");

        //建造X201计算机
        Computer x201 = computerDirector.constructX201();
        //输出X201的计算机配置
        System.out.println(x201);
    }
}

输出结果如下

型号: Thinkpad T410
CPU i5-450
内存  4G 1333MHz
硬盘  500G 7200转
显卡  Nvidia
显示器 14英寸 1280*800
操作系统    Windows7 旗舰版
------------我是分割线----------------
型号: Thinkpad X201
CPU i3-350
内存  2G 1333M
硬盘  250G 5400 转
显示器 12英寸 1280*800
操作系统    Windows7 Home版

好了,经过上面的步骤,我们的产品就建造好咯。


这时候有人会说,你这个例子还是不接地气啊,我怎么没见过什么程序这么写呢,好,那咋就来一个接地气的例子,看下面的代码:

AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setTitle("我是标题")
                .setIcon(R.drawable.icon)
                .show();

上面是一个Android里面警告框的例子,我们可以通过链式调用的方法将标题和图标传入,然后调用show()方法就构建了一个警告框。这个例子是不是很常见,那我们就用一个类使用建造者模式实现一下吧:

package com.codekong.my;

import javax.naming.Context;

public class MyDialog {
    //警告框标题
    private String title;
    //警告框图标资源Id
    private int iconId;
    //上下文环境
    private Context context;
    public String getTitle() {
        return title;
    }

    public int getIconId() {
        return iconId;
    }

    public Context getContext() {
        return context;
    }

    public static class Builder{
        //设置默认值
        private String title = "Title";
        private int iconId = 0;
        private Context context;
        public Builder(Context context){
            this.context = context;
        }

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

        public Builder setIconId(int iconId) {
            this.iconId = iconId;
            return this;
        }

        //应用我们的设置
        private void applyConfig(MyDialog myDialog){
            myDialog.title = title;
            myDialog.iconId = iconId;
            myDialog.context = context;
        }

        public MyDialog show(){
            MyDialog myDialog = new MyDialog();
            applyConfig(myDialog);
            return myDialog;
        }

    }
}

上面的类主要涉及到以下几步:

1 . 创建一个类,先声明他的成员变量以及成员变量的get方法(其实这一步就是建造者模式里面的产品角色,get方法是为了我们使用时可以随时查看我们自定义的产品属性)

2 . 定义一个静态内部类Builder,然后把我们产品定义的属性在静态内部类中复制一份,同时生成它的set方法(这一步呢其实就是我们的抽象建造者角色,要注意的一点是为了实现链式调用,我们要让我们的set方法返回值为Builder, 同时在set方法中返回this,也就是返回本对象)

3 . 接着定义applyConfig()方法,把我们通过set方法设置的值全部赋值给我们的外部类对应的成员变量(这一步就是我们的具体的建造者角色)

4 . 最后对外提供一个show()方法,在其中先 new 出一个我们的MyDialog对象,然后把它传入调用applyConfig()方法,调用过后我们的myDialog对象就已经被设置属性了,我们此时就可以将这个设置过的对象传到外部供其他类使用(这一步就是我们的导演角色)

当我们使用的时候就可以通过下面代码使用:

MyDialog myDialog = new MyDialog.Builder(this)
        .setTitle("我是标题")
        .setIconId(R.drawable.icon)
        .show();

六、后记

以上就是建造者模式的全部内容,希望可以帮助到有需要的人.

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

推荐阅读更多精彩内容

  • 模式定义 建造者模式:将一个复杂产品的创建与表示分离,使得同样的创建过程可以创建不同的表示客户端不用去关心产品对象...
    C_zx阅读 487评论 1 5
  • 1、初识建造者模式 建造者模式属于创建型模式。比如说:楼房是千差万别的,楼房的外形,层数,内部房间的数量,房间的装...
    唠嗑008阅读 456评论 0 2
  • 建造者模式 定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 首先这是一个复杂的对...
    晨鸣code阅读 809评论 0 1
  • 建造者模式 想象一下,我们想要创建一个由多个部分构成的对象,而且它的构成需要一步接一步地完成。只有当各个部分都创建...
    英武阅读 2,157评论 1 50
  • 阴雨绵绵的天气,依然遮挡不住春光满园
    林雨霖阅读 263评论 0 0