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

程序媛和她的猫

共 20964字,需浏览 42分钟

 · 2021-03-26

一、定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。

建造者模式包含的角色:
1、产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
2、抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getProduct()。
3、具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
4、指挥者(Director)它调用建造者对象中的部件构造与装配方法完成复杂对象的创建。其中包含一个负责组装的方法void makeProduct(Builder builder),在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

建造者模式UML图

二、特点

1、它将一个复杂的对象分解为多个简单的对象,然后分别构建这些个简单对象,最后再组合成我们需要的对象。

生活中就有类似建造者模式的例子,例如计算机(产品,Product)是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,买家(客户端,Client)不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司(指挥者,Director),计算机销售公司安排技术人员(建造者,Builder)去组装计算机,然后再交给买家。

2、它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

在上述的例子中,CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标这些部件是多种多样的,比如显卡有集成显卡、独立显卡,CPU有i7处理器、i5处理器、i3处理器等等,计算机的部件就是这几种,但是使用不同的部件,创建出来的计算机就是不一样的,这是可以根据需要灵活选择的。

三、建造者模式和工厂模式

建造者模式很容易和工厂模式相混淆,下面我们来分析一下二者的区别。

在工厂模式中,工厂对象会生产出一个产品对象,客户端有可能把这些产品组装成一个更大和更复杂的产品,也有可能不会。工厂对象是没有状态的,不知道上一次构建的是哪一个产品,不知道下一次构建的是哪一个产品,更不知道自己构建的产品在更高层的产品结构中是什么位置。

建造者模式的重点在指挥者这个角色。指挥者对象是有状态的,它知道产品的整体蓝图,知道上一次、这一次和下一次交给建造者角色去构建的零件是什么,以便能够将这些零件组装成为一个更大规模的产品。它一点一点地建造出一个复杂的产品,而这个产品的组装程序就发生在指挥者角色内部。建造者模式的客户端拿到的是一个完整的最后产品。

换言之,虽然工厂模式与建造者模式都是生产出一个产品,但是工厂模式处在更加具体的尺度上,而建造者模式则处于更加宏观的尺度上。一个系统可以由一个建造者模式和一个抽象工厂模式组成,工厂模式注重零部件的创建过程,建造者模式注重零部件的组装过程。工厂模式创造出产品的不同零件,而建造者模式则把它们组装成最后的产品。以众神造人为例,女娲利用建造者模式负责把灵魂、耳目、手臂等组合成一个完整的人,而黄帝、上骈、桑林各自利用工厂模式创造出灵魂、耳目、臂手等。女娲不必考虑灵魂、耳目、手臂是什么样子、怎么创造出来的。这就成为一个由建造者模式和工厂模式组合而成的系统。

四、建造者模式的优点

  • 易于解耦
    将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品,也就说细节依赖于抽象
  • 易于精确控制对象的创建
    将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
  • 易于拓展
    增加新的具体建造者无需修改原有类库的代码,只需再增加一个具体建造者即可,易于拓展,符合“开闭原则”。
    每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。

五、建造者模式的缺点

1、建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

2、如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

六、建造者模式的适用场景(相比较使用类的构造器new对象,使用创建者模式有什么好处)

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用建造者模式。

使用类的构造器new对象:
1、一旦创建的对象所需的参数变了,就要新增加一个构造器,这样会造成构造器的冗余。

使用建造者模式:
1、使用建造者模式的链式赋值,可以解决构造器冗余的问题。
2、当客户端使用一个对象的时候,我们希望只返回这个对象,而不需要关注其内部的创建细节,建造者模式符合这个类设计原则。

七、建造者模式示例(举例讲解为什么使用建造者模式)

1、使用类的构造器
// 计算机
public class Computer {
    private String cpu;// CPU,必选
    private String ram;// 内存,必选
    private String graphicsCard;// 显卡,必选
    private String display;// 显示器,可选
    private String keyboard;// 键盘,可选

    // 构造器,所有零件都有
    public Computer(String cpu, String ram, String graphicsCard, String display, String keyboard) {
        this.cpu = cpu;
        this.ram = ram;
        this.graphicsCard = graphicsCard;
        this.display = display;
        this.keyboard = keyboard;
    }

    // 构造器,只包含基础的零件:CPU、内存和显卡
    public Computer(String cpu, String ram, String graphicsCard) {
        this.cpu = cpu;
        this.ram = ram;
        this.graphicsCard = graphicsCard;
    }

    // 构造器,包含基础的零件,还有显示器
    public Computer(String cpu, String ram, String graphicsCard, String keyboard) {
        this.cpu = cpu;
        this.ram = ram;
        this.graphicsCard = graphicsCard;
        this.keyboard = keyboard;
    }

    // 。。。其他构造器

    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 getGraphicsCard() {
        return graphicsCard;
    }

    public void setGraphicsCard(String graphicsCard) {
        this.graphicsCard = graphicsCard;
    }

    public String getDisplay() {
        return display;
    }

    public void setDisplay(String display) {
        this.display = display;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", graphicsCard='" + graphicsCard + '\'' +
                ", display='" + display + '\'' +
                ", keyboard='" + keyboard + '\'' +
                '}';
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        // 客户1想要一台CPU:i3,内存:SDRAM,显卡:集成显卡的计算机,不需要显示器和键盘
        Computer computer1 = new Computer("i3","SDRAM","集成显卡");
        System.out.println("客户1购买的计算机:" + computer1.toString());

        // 客户2想要一台包含所有零件的电脑,CPU:i7,内存:RDRAM,显卡:独立显卡,显示器:三星24寸,键盘:罗技
        Computer computer2 = new Computer("i7""RDRAM""独立显卡""三星24寸""罗技");
        System.out.println("客户2购买的计算机:" + computer2.toString());

        // 其他客户想要其他类型的电脑
    }
}

程序运行结果:

客户1购买的计算机:Computer{cpu='i3', ram='SDRAM', graphicsCard='集成显卡', display='null', keyboard='null'}
客户2购买的计算机:Computer{cpu='i7', ram='RDRAM', graphicsCard='独立显卡', display='三星24寸', keyboard='罗技'}

从上面的例子可以看出来,当构造器的参数很多时,使用类的构造器创建对象有如下缺点:

1、阅读不方便,当看到一个对象,我们需要在多个构造器中找到它用的是哪个构造器。
2、当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。

2、使用建造者模式

(1)、传统的建造者模式

产品角色(Product)

// 计算机
public class Computer {
    private String cpu;// CPU,必选
    private String ram;// 内存,必选
    private String graphicsCard;// 显卡,必选
    private String display;// 显示器,可选
    private String keyboard;// 键盘,可选

    // 构造器参数:必选的三个子部件
    public Computer(String cpu, String ram, String graphicsCard) {
        this.cpu = cpu;
        this.ram = ram;
        this.graphicsCard = graphicsCard;
    }

    // 可选子部件使用set方法赋值
    public void setDisplay(String display) {
        this.display = display;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", graphicsCard='" + graphicsCard + '\'' +
                ", display='" + display + '\'' +
                ", keyboard='" + keyboard + '\'' +
                '}';
    }
}

抽象建造者(Builder)

public abstract class ComputerBuilder {
    // 创建产品可选子部件的方法
    public abstract void setDisplay();
    public abstract void setKeyboard();
    // 返回最终组装好的复杂产品的方法
    public abstract Computer getComputer();
}

具体建造者(Concrete Builder)

// 苹果电脑建造者类
public class MacComputerBuilder extends ComputerBuilder {

    private Computer computer;
    public MacComputerBuilder(String cpu, String ram, String graphicsCard) {
        computer = new Computer(cpu, ram, graphicsCard);
    }

    @Override
    public void setDisplay() {
        computer.setDisplay("苹果显示器");
    }

    @Override
    public void setKeyboard() {
        computer.setKeyboard("苹果键盘");
    }

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

// 联想电脑建造者类
public class LenovoComputerBuilder extends ComputerBuilder {

    private Computer computer;
    public LenovoComputerBuilder(String cpu, String ram, String graphicsCard) {
        computer = new Computer(cpu, ram, graphicsCard);
    }

    @Override
    public void setDisplay() {
        computer.setDisplay("联想显示器");
    }

    @Override
    public void setKeyboard() {
        computer.setKeyboard("联想键盘");
    }

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

指挥者(Director)

public class ComputerDirector {
    // 组装产品的方法
    public void makeComputer(ComputerBuilder builder){
        builder.setDisplay();
        builder.setKeyboard();
    }
}

客户端(Client)

public class Client {
    public static void main(String[] args) {
        ComputerDirector director = new ComputerDirector();
        ComputerBuilder builder = new MacComputerBuilder("I5处理器","SDRAM""集成显卡");
        director.makeComputer(builder);
        Computer macComputer = builder.getComputer();
        System.out.println("mac computer:" + macComputer.toString());

        ComputerBuilder lenovoBuilder = new LenovoComputerBuilder("I7处理器","RDRAM""独立显卡");
        director.makeComputer(lenovoBuilder);
        Computer lenovoComputer = lenovoBuilder.getComputer();
        System.out.println("lenovo computer:" + lenovoComputer.toString());
    }
}

程序运行结果:

mac computer:Computer{cpu='I5处理器', ram='SDRAM', graphicsCard='集成显卡', display='苹果显示器', keyboard='苹果键盘'}
lenovo computer:Computer{cpu='I7处理器', ram='RDRAM', graphicsCard='独立显卡', display='联想显示器', keyboard='联想键盘'}

(2)、在实际开发过程中使用的建造者模式
实际在开发中,Director常常被忽略,而是直接通过Builder的方式链式组装,同时Builder是产品Product的静态内部类。

产品角色(Product),Builder是其静态内部类

// 计算机
public class Computer {
    private String cpu;// CPU,必选
    private String ram;// 内存,必选
    private String graphicsCard;// 显卡,必选
    private String display;// 显示器,可选
    private String keyboard;// 键盘,可选

    private Computer(Builder builder){
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.graphicsCard = builder.graphicsCard;
        this.display = builder.display;
        this.keyboard = builder.keyboard;
    }

    // Builder,静态内部类
    public static class Builder{
        private String cpu;// 必选
        private String ram;// 必选
        private String graphicsCard;// 必选
        private String display;//可选
        private String keyboard;//可选

        public Builder(String cup, String ram, String graphicsCard){
            this.cpu = cup;
            this.ram = ram;
            this.graphicsCard = graphicsCard;
        }

        public Builder setDisplay(String display) {
            this.display = display;
            return this;
        }

        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }

        //省略getter方法

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

    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", graphicsCard='" + graphicsCard + '\'' +
                ", display='" + display + '\'' +
                ", keyboard='" + keyboard + '\'' +
                '}';
    }
}

客户端(Client),使用链式调用,一步一步的把对象构建出来。

public class Client {
    public static void main(String[] args) {
        Computer macComputer=new Computer.Builder("I5处理器","SDRAM""集成显卡")
                .setDisplay("苹果显示器")
                .setKeyboard("苹果键盘")
                .build();
        System.out.println("mac computer:" + macComputer.toString());

        Computer lenovoComputer=new Computer.Builder("I7处理器","RDRAM""独立显卡")
                .setDisplay("联想显示器")
                .setKeyboard("联想键盘")
                .build();
        System.out.println("lenovo computer:" + lenovoComputer.toString());
    }
}

程序运行结果:

mac computer:Computer{cpu='I5处理器', ram='SDRAM', graphicsCard='集成显卡', display='苹果显示器', keyboard='苹果键盘'}
lenovo computer:Computer{cpu='I7处理器', ram='RDRAM', graphicsCard='独立显卡', display='联想显示器', keyboard='联想键盘'}


浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报