设计模式建造者模式
# 建造者模式
1.简介
建造者模式是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相关的创建代码生成不同类型和形式的对象。
2.uml图
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
。其他三个为非必选参数,那么我们通常需要如何去构造电脑类的实例呢。
- 首先第一种方式就是构造方法,通过构造函数去实现对象的创建。但是当参数类型数量过多的时候,不同的实例所需要的构造函数也不同,就会导致构造函数特别多。同时如果需要构造函数的参数类型都是一致的,比如说都为
int
或String
就会导致参数顺序十分容易写错,接手的人维护也不易。 - 第二种方式就是 使用
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…