生产者 Builder
建造者模式(Builder)使用多个简单的对象一步一步构建成一个复杂的对象。
适用场景
在软件开发中,存在大量复杂对象,它们拥有一系列成员变量和嵌套对象需要对进行繁复的初始化工作。而且在这些复杂对象中,还可能存在一些限制条件,比如:
- 某些属性没有赋值则复杂对象不能作为一个完整的产品使用。
- 有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值。
为了解决这些复杂的初始化工作,设计了生成器进行初始化和组装,并使用主管的方式来规定不同生产线的流程和配置。
核心角色
- 生成器(Builder):抽象产品生产步骤的方法。
- 具体生成器(Concrete Builders):产品生产步骤方法的不同的具体实现。
- 产品(Products):最终生成的对象。里面包含各种组件,用过生成器将组件装配到产品中生产特定的产品。
- 主管(Director): 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
场景模拟
假如我们需要生成一台电脑(产品)和它的配置说明书(产品)给用户,假设一台电脑(产品)的组件有CPU、主板、电源、内存、硬盘组成,其中的属性如下图所示:
组件名称 | CPU | 主板 | 电源 | 内存 | 硬盘 | ||
组件属性 | CPU类型 | 主板规模 | 电源瓦数 | 内存型号 | 内存容量 | 硬盘类型 | 硬盘容量 |
属性值 | Intel / AMD | ITX / MATX / ATX | 500~2000 | DDR4 / DDR5 | 2~16 | 机械硬盘 / 固态硬盘 | 1024~16384 512~4096 |
其中,整个生成器结构可以用下表表示:
层级 | 应用层 | 中间层-主管 | 中间层-生成器 | 底层-产品 | 底层-组件 |
生成器接口 | |||||
类 | 客户端 | 主管类 | 电脑生成器 | 电脑类(产品) | CPU、主板、电源、内存、硬盘 |
电脑配置说明生成器 | 电脑配置说明类(产品) |
实现流程
-
清晰地定义成员,确保它们可以制造所有形式的产品。
明确生成一台电脑和一本电脑配置说明需要设置好CPU、主板、电源、内存、硬盘这几个属性。
-
定义产品(电脑、电脑说明)和组件(CPU、主板、电源、内存、硬盘)。
产品
// 电脑 public class Computer { private int price; private Cpu cpu; private Mainboard mainboard; private PowerSupply powerSupply; private Memory memory; private HardDisk hardDisk; // 记得写参数构造函数 public Computer(int price, Cpu cpu, Mainboard mainboard, PowerSupply powerSupply, Memory memory, HardDisk hardDisk) { this.price = price; this.cpu = cpu; this.mainboard = mainboard; this.powerSupply = powerSupply; this.memory = memory; this.hardDisk = hardDisk; } // 构造函数整体set,这里只提供get函数 public int getPrice() { return price; } public Cpu getCpu() { return cpu; } public Mainboard getMainboard() { return mainboard; } public PowerSupply getPowerSupply() { return powerSupply; } public Memory getMemory() { return memory; } public HardDisk getHardDisk() { return hardDisk; } } // 电脑说明书 public class ComputerManual { private int price; private Cpu cpu; private Mainboard mainboard; private PowerSupply powerSupply; private Memory memory; private HardDisk hardDisk; // 记得写参数构造函数 public ComputerManual(int price, Cpu cpu, Mainboard mainboard, PowerSupply powerSupply, Memory memory, HardDisk hardDisk) { this.price = price; this.cpu = cpu; this.mainboard = mainboard; this.powerSupply = powerSupply; this.memory = memory; this.hardDisk = hardDisk; } // 构造函数整体set,这里只提供get函数 public int getPrice() { return price; } public Cpu getCpu() { return cpu; } public Mainboard getMainboard() { return mainboard; } public PowerSupply getPowerSupply() { return powerSupply; } public Memory getMemory() { return memory; } public HardDisk getHardDisk() { return hardDisk; } @Override public String toString() { return "ComputerManual{" + "price=" + price + ", cpu=" + cpu + ", mainboard=" + mainboard + ", powerSupply=" + powerSupply.toString() + ", memory=" + memory.toString() + ", hardDisk=" + hardDisk.toString() + '}'; } }
组件
// CPU public enum Cpu { Intel,AMD } // 主板 public enum Mainboard { ITX, MATX, ATX } // 电源 public class PowerSupply { private int power; public PowerSupply(int power) { if (power >= 500 && power <= 2000){ this.power = power; }else { System.out.println(String.format("%s w is invalid power!")); } } public int getPower() { return power; } @Override public String toString() { return "PowerSupply{" + "power=" + power + '}'; } } // 内存 public enum MemoryType { DDR4, DDR5 } // and public class Memory { private MemoryType memoryType; private int capacity; public Memory(MemoryType memoryType, int capacity) { this.memoryType = memoryType; if (capacity >= 2 && capacity <= 16) { this.capacity = capacity; } else { System.out.println(String.format("%s g is invalid memory capacity!")); } } public MemoryType getMemoryType() { return memoryType; } public int getCapacity() { return capacity; } @Override public String toString() { return "Memory{" + "memoryType=" + memoryType + ", capacity=" + capacity + '}'; } } // 硬盘 public enum HardDiskType { Mechanical, Solid } // and public class HardDisk { private HardDiskType hardDiskType; private int capacity; public HardDisk(HardDiskType hardDiskType, int capacity) { this.hardDiskType = hardDiskType; if (hardDiskType == HardDiskType.Mechanical) { if (capacity >= 1024 && capacity <= 16384) { this.capacity = capacity; } else { System.out.println(String.format("%s g is invalid mechanical hard disk capacity!")); } } else if (hardDiskType == HardDiskType.Solid) { if (capacity >= 512 && capacity <= 4096) { this.capacity = capacity; } else { System.out.println(String.format("%s g is invalid solid hard disk capacity!")); } } } public HardDiskType getHardDiskType() { return hardDiskType; } public int getCapacity() { return capacity; } @Override public String toString() { return "HardDisk{" + "hardDiskType=" + hardDiskType + ", capacity=" + capacity + '}'; } }
-
在基本生成器接口中声明这些步骤。
public interface Builder { // 只设计必要set函数,不设计输出完整产品的函数 void setCpu(Cpu cpu); void setMainboard(Mainboard mainboard); void setPowerSupply(PowerSupply powerSupply); void setMemory(Memory memory); void setHardDisk(HardDisk hardDisk); }
-
为每个形式的产品创建具体生成器类,并实现其构造步骤。PS:不要忘记实现获取构造结果对象的方法,也就是下文的getComputer()和getComputerManual()。你不能在生成器接口中声明该方法,因为不同生成器构造的产品可能没有公共接口(Computer和ComputerManual),因此你就不知道该方法返回的对象类型。
// 电脑生成器 public class ComputerBuilder implements Builder { private int price; private Cpu cpu; private Mainboard mainboard; private PowerSupply powerSupply; private Memory memory; private HardDisk hardDisk; public void setPrice(int price) { this.price = price; } @Override public void setCpu(Cpu cpu) { this.cpu = cpu; } @Override public void setMainboard(Mainboard mainboard) { this.mainboard = mainboard; } @Override public void setPowerSupply(PowerSupply powerSupply) { this.powerSupply = powerSupply; } @Override public void setMemory(Memory memory) { this.memory = memory; } @Override public void setHardDisk(HardDisk hardDisk) { this.hardDisk = hardDisk; } // 记得添加获取构造结果对象的方法 public Computer getComputer() { return new Computer(price, cpu, mainboard, powerSupply, memory, hardDisk); } } // 电脑说明书 public class ComputerManualBuilder implements Builder { private int price; private Cpu cpu; private Mainboard mainboard; private PowerSupply powerSupply; private Memory memory; private HardDisk hardDisk; public void setPrice(int price) { this.price = price; } @Override public void setCpu(Cpu cpu) { this.cpu = cpu; } @Override public void setMainboard(Mainboard mainboard) { this.mainboard = mainboard; } @Override public void setPowerSupply(PowerSupply powerSupply) { this.powerSupply = powerSupply; } @Override public void setMemory(Memory memory) { this.memory = memory; } @Override public void setHardDisk(HardDisk hardDisk) { this.hardDisk = hardDisk; } // 记得添加获取构造结果对象的方法 public ComputerManual getComputerManual() { return new ComputerManual(price, cpu, mainboard, powerSupply, memory, hardDisk); } }
-
考虑创建主管类。它可以使用同一生成器对象来封装多种构造产品的方式(在这设计为生产高性能电脑和低配电脑)。
public class Director { // 主管负责设计好参数 public void getHighPerformanceComputers(Builder builder) { builder.setCpu(Cpu.Intel); builder.setMainboard(Mainboard.MATX); builder.setPowerSupply(new PowerSupply(2000)); builder.setMemory(new Memory(MemoryType.DDR5, 16)); builder.setHardDisk(new HardDisk(HardDiskType.Solid, 4096)); } public void getLowPerformanceComputers(Builder builder) { builder.setCpu(Cpu.Intel); builder.setMainboard(Mainboard.ITX); builder.setPowerSupply(new PowerSupply(500)); builder.setMemory(new Memory(MemoryType.DDR4, 2)); builder.setHardDisk(new HardDisk(HardDiskType.Mechanical, 1024)); } }
-
客户端代码会同时创建生成器和主管对象。构造开始前,客户端必须将生成器对象传递给主管对象。通常情况下,客户端只需调用主管类构造函数一次即可。主管类使用生成器对象完成后续所有制造任务。还有另一种方式,那就是客户端可以将生成器对象直接传递给主管类的制造方法。
public class Application { public static void main(String[] args) { Director director = new Director(); // 创建电脑生成器 ComputerBuilder computerBuilder = new ComputerBuilder(); ComputerManualBuilder computerManualBuilder = new ComputerManualBuilder(); // 主管设置生成器参数 director.getHighPerformanceComputers(computerBuilder); director.getHighPerformanceComputers(computerManualBuilder); // 从配置好的生成器中获取产品 Computer computer = computerBuilder.getComputer(); ComputerManual computerManual = computerManualBuilder.getComputerManual(); System.out.println("The Computer is:" + computerManual.toString()); } }
-
只有在所有产品都遵循相同接口的情况下,构造结果可以直接通过主管类获取。否则,客户端应当通过生成器获取构造结果。
使用注意
- 组件和产品中一般会写一个参数构造函数便于主管类和生成器填写配置使用。
- Builder接口中只设计必要set函数,不设计输出完整产品的函数(因为返回类不统一)
- 接上一条,实现Builder接口的生成器记得最后写一个获取产品的函数。