设计模式建造者模式

2023年 7月 25日 33.5k 0

# 建造者模式

1.简介

建造者模式是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相关的创建代码生成不同类型和形式的对象。

2.uml图

建造者模式1.png

3.示例

首先我们来看一看传统的建造者模式:

Product: 最终要生成的对象,例如 Computer实例。
Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
ConcreteBuilder: Builder的实现类。
Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

主要核心就是将一系列的生成步骤(Builder)和创建产品的一系列生成器步骤调用(Director)抽象出来,使其解耦开来。严格来说程序中并非一定需要Director对象,但是Director中非常适合放入各种例行构造流程, 以便在程序中反复使用。

Computer类:

package com.gs.designmodel.builder.tradition;

/**
 * @author: Gaos
 * @Date: 2023-07-19 16:37
 *
 * 目标电脑类
 **/
public class Computer {

    private String cpu; // 必选

    private String ram;// 必选

    private int usbCount;

    private String keyBoard;

    private String display;

    public Computer(String cpu, String ram) {
        this.cpu = cpu;
        this.ram = ram;
    }

    public void setUsbCount(int usbCount) {
        this.usbCount = usbCount;
    }

    public void setKeyBoard(String keyBoard) {
        this.keyBoard = keyBoard;
    }

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

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

抽象构建者类:

package com.gs.designmodel.builder.tradition;

/**
 * @author: Gaos
 * @Date: 2023-07-19 16:46
 *
 * 抽象构建者类
 **/
public abstract class ComputerBuilder {

    public abstract void setUsbCount();

    public abstract void setKeyBoard();

    public abstract void setDisplay();

    public abstract Computer getComputer();
}

实体构建者类--->苹果电脑构建者类

package com.gs.designmodel.builder.tradition;

/**
 * @author: Gaos
 * @Date: 2023-07-19 16:47
 *
 * 苹果电脑构建者类
 **/
public class MacComputerBuilder extends ComputerBuilder{

    private Computer computer;

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

    @Override
    public void setUsbCount() {
        computer.setUsbCount(2);
    }

    @Override
    public void setKeyBoard() {
        computer.setKeyBoard("Mac键盘");
    }

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

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

实体构建者类-->联想电脑构建者类

package com.gs.designmodel.builder.tradition;

/**
 * @author: Gaos
 * @Date: 2023-07-19 16:50
 **/
public class LenovoComputerBuilder extends ComputerBuilder{

    private Computer computer;

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

    @Override
    public void setUsbCount() {
        computer.setUsbCount(4);
    }

    @Override
    public void setKeyBoard() {
        computer.setKeyBoard("Lenovo键盘");
    }

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

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

Director 指导者类

package com.gs.designmodel.builder.tradition;

/**
 * @author: Gaos
 * @Date: 2023-07-19 16:56
 *
 * 指导者类(Director)
 **/
public class ComputerDirector {

    public void makeComputer(ComputerBuilder builder) {
        builder.setUsbCount();
        builder.setDisplay();
        builder.setKeyBoard();
    }
}

测试:

package com.gs.designmodel.builder.tradition;

/**
 * @author: Gaos
 * @Date: 2023-07-19 16:57
 *
 * 首先生成一个director->生成一个目标builder->使用director组装builder->组装完毕后创建产品实例
 **/
public class Test {
    public static void main(String[] args) {
        ComputerDirector director = new ComputerDirector();

        MacComputerBuilder builder = new MacComputerBuilder("I5处理器", "三星122");

        director.makeComputer(builder);

        Computer macComputer = builder.getComputer();
        System.out.println("mac computer :" + macComputer.toString());

        LenovoComputerBuilder lenovoBuilder = new LenovoComputerBuilder("I7处理器", "海尔155");
        director.makeComputer(lenovoBuilder);
        Computer lenovoComputer = lenovoBuilder.getComputer();
        System.out.println("lenovo computer: " + lenovoComputer.toString());

    }
}

结果:

mac computer :Computer{cpu='I5处理器', ram='三星122', usbCount=2, keyBoard='Mac键盘', display='Mac显示器'}
lenovo computer: Computer{cpu='I7处理器', ram='海尔155', usbCount=4, keyBoard='Lenovo键盘', display='Lenovo显示器'}

再下来我们再来看一下目前比较流行的建造者模式:

首先我们上述电脑类中有两个必选参数,cpu ram。其他三个为非必选参数,那么我们通常需要如何去构造电脑类的实例呢。

  • 首先第一种方式就是构造方法,通过构造函数去实现对象的创建。但是当参数类型数量过多的时候,不同的实例所需要的构造函数也不同,就会导致构造函数特别多。同时如果需要构造函数的参数类型都是一致的,比如说都为 intString就会导致参数顺序十分容易写错,接手的人维护也不易。
  • 第二种方式就是 使用set方法构造实例,其实也就是使用构造函数(空的或者带必选参数的)方法去生成实例并含有必要的属性,再使用set方法去对其他属性赋值。这种对象的创建方式可以有效的防止赋值属性错乱的问题,因为看上去一目了然,基本上不会出错,代码可读性也很强。但是一方面如果字段多了,对应的代码行数也会比较多。另一种是如果我希望这个对象构建完后,就不可以再被修改了,这一点也是set方法做不到的,因为他就是提供给调用者使用的,在这种情况下可能会存在一些安全隐患。

在这中情况下一种新型的建造者模式就出现了,类似像lombok中给对象加了@Builder注解之后的创建方式。因为lombok中的@Builder就是建造者模式。我们使用这种方式来对上面的代码进行实现。

大概的思路:

1、在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
2、在Computer中创建一个private的构造函数,参数为Builder类型
3、在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
4、在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
5、在Builder中创建一个build()方法,在其中构建Computer的实例并返回

按照上述的思路改造原本的 Computer类:

package com.gs.designmodel.builder.popularity;

/**
 * @author: Gaos
 * @Date: 2023-07-20 10:49
 *
 * 链式调用的Computer类
 **/
public class Computer {

    private final String cpu; // 必选

    private final String ram; // 必选

    private final int usbCount;

    private final String keyBoard;

    private final String display;

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

    public static class Builder {
        private String cpu;//必须
        private String ram;//必须
        private int usbCount;//可选
        private String keyboard;//可选
        private String display;//可选

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

        public Builder setUsbCount(int usbCount) {
            this.usbCount = usbCount;
            return this;
        }
        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }
        public Builder setDisplay(String display) {
            this.display = display;
            return this;
        }
        public Computer build(){
            return new Computer(this);
        }
    }

}

同时返回自身来实现链式调用,下面来进行一下测试类:

package com.gs.designmodel.builder.popularity;


/**
 * @author: Gaos
 * @Date: 2023-07-20 11:35
 **/
public class Test {
    public static void main(String[] args) {
        Computer computer=new Computer.Builder("因特尔","三星")
                .setDisplay("三星24寸")
                .setKeyboard("罗技")
                .setUsbCount(2)
                .build();
    }
}

结果:

Computer{cpu='因特尔', ram='三星', usbCount=2, keyBoard='罗技', display='三星24寸'}

可能有人会有疑惑,为什么不能写成这种模式:

Computer = new Computer("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2);

这里我个人的理解是建造者模式是不能依赖于set方法的,它需要对构建过程闭合不能向外暴露方法。假如lombok中的@Builder中使用的是这种,那不就以为着使用这个注解的对象默认对外暴露所有的修改方法吗,这与建造者模式本身的理念是相反的,缺少了builder这一层。

这种模式其实就是取消掉了director这一层。将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用。

4.总结

使用建造者模式可以避免重叠构造函数的出现,你可以使用建造者模式创建不同形式的产品,也可以用来构造复杂对象。

建造者模式重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。

相关参考文章:

blog.csdn.net/ShuSheng000…

refactoringguru.cn/design-patt…

juejin.cn/post/684490…

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论