建造者模式
微信公众号:新时代程序猿
关注可了解更多的JAVA,PYTHON,ANDROID教程及开发技术。
问题或建议,请公众号留言;
如果你觉得文章对你有帮助,欢迎赞赏
建造者模式
前言
还是那句话,没有敲个几万行代码,谈设计模式都是瞎扯淡,咱先看看定义
在软件工程领域,设计模式是一套通用的可复用的解决方案,用来解决在软件设计过程中产生的通用问题。它不是一个可以直接转换成源代码的设计,只是一套在软件系统设计过程中程序员应该遵循的最佳实践准则。
所以说,没有工作经验或者没有项目经验,你会完全不知道设计模式在搞什么鬼~你就记住了,假如没有设计模式,软件照样开发,就是在大型软件系统开发及维护过程中就痛苦不堪,最后在不断重构后你会发现尼玛竟然用了好多设计模式。
建造者模式的定义和特点
定义虽然基本没有卵用,因为大部分人都看不懂,但是还的说出来
The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示
使用场景
设计模式的核心不在于你记住某一段代码,而是在于懂得某个设计模式的思想,知道在什么情况下使用什么设计模式,这样你的代码才能易于维护,下面是建造者设计模式的使用场景:
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
解决的问题
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。例如我们现在有如下一个类计算机类Computer,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:
public class Computer {
private String cpu;//芯片必须
private String ram;//内存必须
private int usbCount;//usb转换器可选
private String keyboard;//键盘可选
private String display;//显示器可选
}
第一:折叠构造函数模式(telescoping constructor pattern),这个我们经常用,如下代码所示
package designmode.factorypattern.buildpatter;
public class Computer {
private String cpu;
private String ram;
private String mouse;
private String keyboard;
private String display;
public Computer(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public Computer(String cpu, String ram, String mouse) {
this.cpu = cpu;
this.ram = ram;
this.mouse = mouse;
}
public Computer(String cpu, String ram, String mouse, String keyboard) {
this.cpu = cpu;
this.ram = ram;
this.mouse = mouse;
this.keyboard = keyboard;
}
public Computer(String cpu, String ram, String keyboard, String display, String mouse) {
this.cpu = cpu;
this.ram = ram;
this.keyboard = keyboard;
this.display = display;
this.mouse = mouse;
}
}
第二种:Javabean 模式,如下所示
public class Computer {
...
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 getMouse() {
return mouse;
}
public void setMouse(String mouse) {
this.mouse = mouse;
}
public String getKeyboard() {
return keyboard;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public String getDisplay() {
return display;
}
public void setDisplay(String display) {
this.display = display;
}
}
那么这两种方式有什么弊端呢?
第一种在穿参数的时候需要一一对应,一两个参数还好,如果是七八个参数,想想都很烦,传错了还难定位原因,熟悉代码的时候也不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。那酸爽谁用谁知道。
第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为那个类中的属性是分步设置的,所以就容易出错。
为了解决这两个问题,builder设计模式就出现了。
建造者模式的结构与实现
- 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
- 在Computer中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
- 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个build()方法,在其中构建Computer的实例并返回
public class Computer {
private String cpu;
private String hardDisk;
private String mouse;
private String display;
private Computer(Builder builder){
this.cpu=builder.cpu;
this.hardDisk=builder.hardDisk;
this.display=builder.display;
this.mouse = builder.mouse;
}
public static class Builder{
private String cpu;//cpu必须
private String hardDisk;//硬盘必须
private String display;//显示屏可选
private String mouse;//鼠标可选
public Builder(String cpu, String hardDisk) {
this.cpu = cpu;
this.hardDisk = hardDisk;
}
public String getCpu() {
return cpu;
}
public String getHardDisk() {
return hardDisk;
}
public String getDisplay() {
return display;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public String getMouse() {
return mouse;
}
public Builder setMouse(String mouse) {
this.mouse = mouse;
return this;
}
public Computer build(){
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", hardDisk='" + hardDisk + '\'' +
", mouse='" + mouse + '\'' +
", display='" + display + '\'' +
'}';
}
}
如何使用
在客户端使用链式调用,一步一步的把对象构建出来。
public class clint {
public static void main(String[] args) {
Computer computer=new Computer.Builder("intel i9","三星")
.setDisplay("三星显示屏")
.setMouse("雷蛇鼠标")
.build();
System.out.println(computer);
}
}
输出:
Computer{cpu='intel i9', hardDisk='三星', mouse='雷蛇鼠标', display='三星显示屏'}
案例
构建者模式是一个非常实用而常见的创建类型的模式(creational design pattern),例如Android开发中图片处理框架Glide,网络请求框架Retrofit等都使用了此模式。
扩展
其实上面的内容是Builder在Java中一种简化的使用方式,经典的Builder 模式与其有一定的不同,如果没有兴趣的同学就可以不用往下读了。
传统Builder 模式
构建者模式UML图如下所示
如上图所示,builder模式有4个角色。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
我们接下来将最开始的例子使用传统方式来实现一遍。
第一步:我们的目标Computer类:
public class ComputerV2 {
private String cpu;
private String hardDisk;
private String mouse;
private String display;
public ComputerV2(String cpu, String hardDisk) {
this.cpu = cpu;
this.hardDisk = hardDisk;
}
public String getMouse() {
return mouse;
}
public void setMouse(String mouse) {
this.mouse = mouse;
}
public String getDisplay() {
return display;
}
public void setDisplay(String display) {
this.display = display;
}
@Override
public String toString() {
return "ComputerV2{" +
"cpu='" + cpu + '\'' +
", hardDisk='" + hardDisk + '\'' +
", mouse='" + mouse + '\'' +
", display='" + display + '\'' +
'}';
}
}
第二步:抽象构建者类
public abstract class ComputerV2Builder {
public abstract void setMouse();
public abstract void setDisplay();
public abstract Computer getComputer();
}
第三步:实体构建者类,我们可以根据要构建的产品种类产生多了实体构建者类,这里我们需要构建两种品牌的电脑,苹果电脑和联想电脑,所以我们生成了两个实体构建者类。
苹果电脑构建者类
public class AppleComputerBuilder extends ComputerV2Builder{
private ComputerV2 computerV2;
public AppleComputerBuilder(String cpu,String diskHard) {
this.computerV2 = new ComputerV2(cpu,diskHard);
}
@Override
public void setMouse() {
computerV2.setMouse("苹果鼠标");
}
@Override
public void setDisplay() {
computerV2.setDisplay("苹果显示器");
}
@Override
public ComputerV2 getComputer() {
return computerV2;
}
}
戴尔电脑构建者类
public class DaleComputerBuilder extends ComputerV2Builder{
private ComputerV2 computerV2;
public DaleComputerBuilder(String cpu, String diskHard) {
this.computerV2 = new ComputerV2(cpu,diskHard);
}
@Override
public void setMouse() {
computerV2.setMouse("戴尔鼠标");
}
@Override
public void setDisplay() {
computerV2.setDisplay("戴尔显示器");
}
@Override
public ComputerV2 getComputer() {
return computerV2;
}
}
第四步:指导者类(Director)
public class ComputerDirector {
public void makeComputer(ComputerBuilder builder){
builder.setUsbCount();
builder.setDisplay();
builder.setKeyboard();
}
}
使用
- 首先生成一个director ,
- 然后生成一个目标builder,
- 接着使用director组装builder,
- 组装完毕后使用builder创建产品实例
ComputerDirector computerDirector = new ComputerDirector();
AppleComputerBuilder appleComputerBuilder = new AppleComputerBuilder("最牛逼CPU", "超快的硬盘");
computerDirector.makeComputer(appleComputerBuilder);
ComputerV2 computer = appleComputerBuilder.getComputer();
System.out.println(computer);
输出结果如下:
ComputerV2{cpu='最牛逼CPU', hardDisk='超快的硬盘', mouse='苹果鼠标', display='苹果显示器'}
可以看到,文章最开始的使用方式是传统builder模式的变种, 首先其省略了director 这个角色,将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用。
该模式的主要优点如下:
封装性好,构建和表示分离。
扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
其缺点如下:
产品的组成部分必须相同,这限制了其使用范围。
如果产品的内部变化复杂,或者产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
评论区