电脑城老板不会告诉你的装机秘籍:建造者模式让你的代码高配起飞!
上一期我们在讲Lombok注解时提到@Builder注解,在讲解这个注解的底层原理之前我们先来深入了解一下设计模式中的建造者设计模式。
什么是建造者模式?
建造者模式是一种创建型的设计思想,它是把一个复杂对象的创建过程拆分成每一个小步,然后一步步构建出来。客户端只需要告诉系统我想要什么,而不用关心怎么创建出来的。
建造者的模式结构
产品类(product)
要构建的复杂对象,包含多个组成部分
抽象建造者接口(Bublider):定义构建产品的各个部分的抽象方法,以及返回最终产品的方法,防止实际建造者漏写关键步骤
具体建造者(ConcreteBuilder)
实现Bublider接口,具体负责各个部分的构建细节,并最终组装成完整产品。
指挥者(director)
:统一指挥建造者按照一定步骤来构建产品,屏蔽了构建过程的细节。
客户端类(Client)
发起建造请求,选择具体的建造者并使用指挥者来完成产品的创建。
案例分析
接下来我举一个实际案例来帮助你理解:
假如你现在要去电脑城选配电脑,
假设电脑可选的配件有 cpu,memory,hardDisk,gpu
那电脑这个产品类的代码如下:
public class Computer {private String cpu ;private String memory ;private String hardDisk ;private String gpu ;
}
传统方法
首先我们先看传统方法实现选配电脑:
1.给实体类添加get/set方法:
public class Computer {private String cpu ;private String memory ;private String hardDisk ;private String gpu ;public Computer() {}public Computer(String cpu, String memory, String hardDisk, String gpu) {this.cpu = cpu;this.memory = memory;this.hardDisk = hardDisk;this.gpu = gpu;}/*** 获取* @return cpu*/public String getCpu() {return cpu;}/*** 设置* @param cpu*/public void setCpu(String cpu) {this.cpu = cpu;}/*** 获取* @return memory*/public String getMemory() {return memory;}/*** 设置* @param memory*/public void setMemory(String memory) {this.memory = memory;}/*** 获取* @return hardDisk*/public String getHardDisk() {return hardDisk;}/*** 设置* @param hardDisk*/public void setHardDisk(String hardDisk) {this.hardDisk = hardDisk;}/*** 获取* @return gpu*/public String getGpu() {return gpu;}/*** 设置* @param gpu*/public void setGpu(String gpu) {this.gpu = gpu;}public String toString() {return "Computer{cpu = " + cpu + ", memory = " + memory + ", hardDisk = " + hardDisk + ", gpu = " + gpu + "}";}
}
2.客户端购买电脑代码
public class Test1 {public static void main(String[] args) {Computer computer = new Computer();computer.setCpu("Intel i7");computer.setMemory("16G");computer.setHardDisk("1T");computer.setGpu("NVIDIA 3080");System.out.println(computer);}
}
执行结果
分析传统方法的弊处
1.参数爆炸,如果选配电脑的配置增加,那构造函数的参数将会特别长
2.无效参数,如果客户只想要配置指定cpu和内存的电脑,其余参数就需要有默认值
3.传统构造函数参数数量固定,参数位置也固定,灵活性太低
4.通过setter修改配置时,有可能会因为遗漏而造成未完全初始化对象的问题
5.配置冲突:无法在编译阶段防止无效配置(如将服务器CPU+游戏显卡组合)
建造者模式的用法
下面我们来讲解建造者模式的用法
1.改写Computer类
package com.cjh.pojo;import lombok.*;@Data
@NoArgsConstructor
public class Computer {private String cpu;private String memory;private String hardDisk;private String gpu;// 静态工厂方法public static Computer create(String cpu, String memory,String hardDisk, String gpu) {Computer computer = new Computer();computer.setCpu(cpu);computer.setMemory(memory);computer.setHardDisk(hardDisk);computer.setGpu(gpu);return computer;}}
2.定义一个建造者接口来声明建造者实现类需要实现的方法(这是一种规范)
public interface ComputerBuilder {ComputerBuilder setCpu(String cpu);ComputerBuilder setMemory(String memory);ComputerBuilder setHardDisk(String hardDisk);ComputerBuilder setGpu(String gpu);Computer build();
}
接口方法里就定义了电脑装机的所有步骤,它要求该接口的实现类要实现所有抽象接口方法,通俗讲就是给制造商指定了硬性规则,防止电脑装机时因为忘记组装部分配件
3.创建建造者实现类实现建造者接口
package com.cjh.builder.builderImpl;import com.cjh.builder.ComputerBuilder;
import com.cjh.pojo.Computer;public class GameComputerBuilderImpl implements ComputerBuilder {private String cpu = "i7-9700K"; // 直接初始化默认值private String memory = "16G";private String hardDisk = "1T";private String gpu = "Nvidia 3080";public GameComputerBuilderImpl(){}@Overridepublic ComputerBuilder setCpu(String cpu) {this.cpu = cpu;return this;}@Overridepublic ComputerBuilder setMemory(String memory) {this.memory = memory;return this;}@Overridepublic ComputerBuilder setHardDisk(String hardDisk) {this.hardDisk = hardDisk;return this;}@Overridepublic ComputerBuilder setGpu(String gpu) {this.gpu = gpu;return this;}@Overridepublic Computer build() {
// computer = new Computer(cpu, memory, hardDisk, gpu);
// reset();return Computer.create(cpu, memory, hardDisk, gpu);}}
这是建造者模式最关键的一大步,接下来让我来解析里面的代码:
private String cpu = "i7-9700K"; // 直接初始化默认值
private String memory = "16G";
private String hardDisk = "1T";
private String gpu = "Nvidia 3080";
这部分私有化属性和Computer是一样的,因为要给Computer组装的配件就是这四个,给这些属性加上默认属性值是因为在建造者模式我们在实例化对象时不是一定要给所有属性赋值的,我们可以给自己想要的属性赋值即可,好比你去配电脑你的要求是我要i5-12400cpu,2t硬盘,其余随意,那老板就给你配i5-12400cpu,2t硬盘其余配件按照默认配置.
@Override
public ComputerBuilder setCpu(String cpu) {this.cpu = cpu;
return this;
}@Override
public ComputerBuilder setMemory(String memory) {
this.memory = memory;
return this;
}@Override
public ComputerBuilder setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
return this;
}@Override
public ComputerBuilder setGpu(String gpu) {
this.gpu = gpu;
return this;
}@Override
public Computer build() {
return Computer.create(cpu, memory, hardDisk, gpu);
}
2.这五个方法是接口方法的具体实现,它实现了通过链式调用来创建对象,这相比之前用构造函数有很大的优势,通过这些方法你可以自定义传入参数的个数,而且不区分传入的位置,更具灵活性。
比如你调用setMemory(8G)它会把原本memory的属性值16G覆盖掉,并给你返回并重新把这个类返回给你,你又可以继续调用其他方法,最终再调用build方法来返回一个Computer的实例对象。
4.假如你去电脑城配电脑,但是你不知道自己要什么配件,你只知道自己想要能玩游戏的电脑,这个时候老板就会拿出一套提前组装好的电脑告诉你这套电脑就适合打游戏。那在代码层面我们要提前组装几个已经完全初始化好的电脑对象就要借助Director类,具体代码如下:
package com.cjh.director;import com.cjh.builder.ComputerBuilder;
import com.cjh.pojo.Computer;public class Director {public Computer constructGamingComputer(ComputerBuilder builder) {return builder.setCpu("i9-12900K").setMemory("32G").setGpu("RTX 4080").build();}public Computer constructOfficeComputer(ComputerBuilder builder) {return builder.setCpu("i5-12400").setMemory("16G").setHardDisk("512G SSD").build(); // 不设置GPU使用核显}
}
准备工作完成后,接下来我们就来测试一下吧,测试代码如下:
public class Main {public static void main(String[] args) {ComputerBuilder computerBuilder = new GameComputerBuilderImpl();Director director = new Director();Computer gameComputer = director.constructGamingComputer(computerBuilder);System.out.println("配置游戏电脑");System.out.println(gameComputer);System.out.println("配置办公电脑");Computer officeComputer = director.constructOfficeComputer(computerBuilder);System.out.println(officeComputer);System.out.println("自定义配置");Computer custom = computerBuilder.setCpu("i5-12400").setMemory("16G").setHardDisk("512G SSD").setGpu("NVIDIA 3080").build();System.out.println(custom);}
}
运行结果
现在你知道建造者的好处了吧?
优点
结构清晰、过程可控:建造者模式把复杂对象的构建过程拆成一个个明确的步骤,让整个构建流程变得清晰、稳定,而且每一步都可以单独控制,方便管理和调整。
便于构建“不同版本”的对象:通过不同的建造者,可以轻松地创建出结构类似但配置不同的对象,特别适合那种“定制化”很强的业务场景,比如生成不同类型的报表、构造不同风格的 UI 等。
代码更易维护和扩展:把构建逻辑从产品本身抽离出来,符合单一职责原则,既减少了耦合,也让代码更容易维护。如果后续要新增构建步骤或者替换某个细节,实现起来也很自然。
缺点
但是建造者模式也是有局限性的
1.增加了很多类,程序员的代码量多了很多
2.建造者模式不是万能的,在有些场景下并不适用
不适合构建过程差异太大的对象:建造者模式适合用在“有共同构建步骤”的对象上。如果对象之间的构建逻辑完全不一样,硬套建造者反而会让代码变得臃肿、不灵活。
回到开头,解决我们的@Builder注解问题,我们的@Builder注解之所以支持我们使用链式调用的形式来创建对象,底层本质上也是通过建造者模式
举例
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class person {private String name;private int age;private String sex;
}
该person类添加了@Builder,所以可以通过链式调用创建对象,测试代码如下;
public class BuilderTest {public static void main(String[] args) {person person1 = person.builder().name("cjh").age(18).sex("男").build();System.out.println(person1);}
运行结果:
因为@Builder注解底层是建造者模式,所以当我们可以不用@Builder注解而是手动通过建造者模式实现链式调用实例化对象。具体代码如下:
package com.cjh.pojo;import lombok.ToString;@ToString
public class Animal {private String name;private int age;private String sex;public Animal(String name, int age, String sex) {this.name = name;this.age = age;this.sex = sex;}public static AnimalBuilder builder() {return new AnimalBuilder();}public static class AnimalBuilder{private String name;private int age;private String sex;public AnimalBuilder name(String name) {this.name = name;return this;}public AnimalBuilder age(int age) {this.age = age;return this;}public AnimalBuilder sex(String sex) {this.sex =sex;return this;}public Animal build() {return new Animal(name, age, sex);}}
}
运行结果
本节课我们通过@Builder注解引出建造者设计模式的探讨,最后通过建造者模式实现对@Builder底层原理的剖析,也解决了上个博客遗留的问题,希望能帮助到大家