设计模式之建造者模式(Builder)
一、定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。
建造者模式包含的角色:
1、产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
2、抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getProduct()。
3、具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
4、指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建。其中包含一个负责组装的方法void makeProduct(Builder builder),在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。
二、特点
1、它将一个复杂的对象分解为多个简单的对象,然后分别构建这些个简单对象,最后再组合成我们需要的对象。
生活中就有类似建造者模式的例子,例如计算机(产品,Product)是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,买家(客户端,Client)不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司(指挥者,Director),计算机销售公司安排技术人员(建造者,Builder)去组装计算机,然后再交给买家。
2、它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
在上述的例子中,CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标这些部件是多种多样的,比如显卡有集成显卡、独立显卡,CPU有i7处理器、i5处理器、i3处理器等等,计算机的部件就是这几种,但是使用不同的部件,创建出来的计算机就是不一样的,这是可以根据需要灵活选择的。
三、建造者模式和工厂模式
建造者模式很容易和工厂模式相混淆,下面我们来分析一下二者的区别。
在工厂模式中,工厂对象会生产出一个产品对象,客户端有可能把这些产品组装成一个更大和更复杂的产品,也有可能不会。工厂对象是没有状态的,不知道上一次构建的是哪一个产品,不知道下一次构建的是哪一个产品,更不知道自己构建的产品在更高层的产品结构中是什么位置。
建造者模式的重点在指挥者这个角色。指挥者对象是有状态的,它知道产品的整体蓝图,知道上一次、这一次和下一次交给建造者角色去构建的零件是什么,以便能够将这些零件组装成为一个更大规模的产品。它一点一点地建造出一个复杂的产品,而这个产品的组装程序就发生在指挥者角色内部。建造者模式的客户端拿到的是一个完整的最后产品。
换言之,虽然工厂模式与建造者模式都是生产出一个产品,但是工厂模式处在更加具体的尺度上,而建造者模式则处于更加宏观的尺度上。一个系统可以由一个建造者模式和一个抽象工厂模式组成,工厂模式注重零部件的创建过程,建造者模式注重零部件的组装过程。工厂模式创造出产品的不同零件,而建造者模式则把它们组装成最后的产品。以众神造人为例,女娲利用建造者模式负责把灵魂、耳目、手臂等组合成一个完整的人,而黄帝、上骈、桑林各自利用工厂模式创造出灵魂、耳目、臂手等。女娲不必考虑灵魂、耳目、手臂是什么样子、怎么创造出来的。这就成为一个由建造者模式和工厂模式组合而成的系统。
四、建造者模式的优点
易于解耦
将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品,也就说细节依赖于抽象。易于精确控制对象的创建
将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。易于拓展
增加新的具体建造者无需修改原有类库的代码,只需再增加一个具体建造者即可,易于拓展,符合“开闭原则”。
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
五、建造者模式的缺点
1、建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
2、如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
六、建造者模式的适用场景(相比较使用类的构造器new对象,使用创建者模式有什么好处)
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用建造者模式。
使用类的构造器new对象:
1、一旦创建的对象所需的参数变了,就要新增加一个构造器,这样会造成构造器的冗余。
使用建造者模式:
1、使用建造者模式的链式赋值,可以解决构造器冗余的问题。
2、当客户端使用一个对象的时候,我们希望只返回这个对象,而不需要关注其内部的创建细节,建造者模式符合这个类设计原则。
七、建造者模式示例(举例讲解为什么使用建造者模式)
1、使用类的构造器
// 计算机
public class Computer {
private String cpu;// CPU,必选
private String ram;// 内存,必选
private String graphicsCard;// 显卡,必选
private String display;// 显示器,可选
private String keyboard;// 键盘,可选
// 构造器,所有零件都有
public Computer(String cpu, String ram, String graphicsCard, String display, String keyboard) {
this.cpu = cpu;
this.ram = ram;
this.graphicsCard = graphicsCard;
this.display = display;
this.keyboard = keyboard;
}
// 构造器,只包含基础的零件:CPU、内存和显卡
public Computer(String cpu, String ram, String graphicsCard) {
this.cpu = cpu;
this.ram = ram;
this.graphicsCard = graphicsCard;
}
// 构造器,包含基础的零件,还有显示器
public Computer(String cpu, String ram, String graphicsCard, String keyboard) {
this.cpu = cpu;
this.ram = ram;
this.graphicsCard = graphicsCard;
this.keyboard = keyboard;
}
// 。。。其他构造器
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 getGraphicsCard() {
return graphicsCard;
}
public void setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
}
public String getDisplay() {
return display;
}
public void setDisplay(String display) {
this.display = display;
}
public String getKeyboard() {
return keyboard;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", graphicsCard='" + graphicsCard + '\'' +
", display='" + display + '\'' +
", keyboard='" + keyboard + '\'' +
'}';
}
}
// 客户端
public class Client {
public static void main(String[] args) {
// 客户1想要一台CPU:i3,内存:SDRAM,显卡:集成显卡的计算机,不需要显示器和键盘
Computer computer1 = new Computer("i3","SDRAM","集成显卡");
System.out.println("客户1购买的计算机:" + computer1.toString());
// 客户2想要一台包含所有零件的电脑,CPU:i7,内存:RDRAM,显卡:独立显卡,显示器:三星24寸,键盘:罗技
Computer computer2 = new Computer("i7", "RDRAM", "独立显卡", "三星24寸", "罗技");
System.out.println("客户2购买的计算机:" + computer2.toString());
// 其他客户想要其他类型的电脑
}
}
程序运行结果:
客户1购买的计算机:Computer{cpu='i3', ram='SDRAM', graphicsCard='集成显卡', display='null', keyboard='null'}
客户2购买的计算机:Computer{cpu='i7', ram='RDRAM', graphicsCard='独立显卡', display='三星24寸', keyboard='罗技'}
从上面的例子可以看出来,当构造器的参数很多时,使用类的构造器创建对象有如下缺点:
1、阅读不方便,当看到一个对象,我们需要在多个构造器中找到它用的是哪个构造器。
2、当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了。。。
2、使用建造者模式
(1)、传统的建造者模式
产品角色(Product)
// 计算机
public class Computer {
private String cpu;// CPU,必选
private String ram;// 内存,必选
private String graphicsCard;// 显卡,必选
private String display;// 显示器,可选
private String keyboard;// 键盘,可选
// 构造器参数:必选的三个子部件
public Computer(String cpu, String ram, String graphicsCard) {
this.cpu = cpu;
this.ram = ram;
this.graphicsCard = graphicsCard;
}
// 可选子部件使用set方法赋值
public void setDisplay(String display) {
this.display = display;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", graphicsCard='" + graphicsCard + '\'' +
", display='" + display + '\'' +
", keyboard='" + keyboard + '\'' +
'}';
}
}
抽象建造者(Builder)
public abstract class ComputerBuilder {
// 创建产品可选子部件的方法
public abstract void setDisplay();
public abstract void setKeyboard();
// 返回最终组装好的复杂产品的方法
public abstract Computer getComputer();
}
具体建造者(Concrete Builder)
// 苹果电脑建造者类
public class MacComputerBuilder extends ComputerBuilder {
private Computer computer;
public MacComputerBuilder(String cpu, String ram, String graphicsCard) {
computer = new Computer(cpu, ram, graphicsCard);
}
@Override
public void setDisplay() {
computer.setDisplay("苹果显示器");
}
@Override
public void setKeyboard() {
computer.setKeyboard("苹果键盘");
}
@Override
public Computer getComputer() {
return computer;
}
}
// 联想电脑建造者类
public class LenovoComputerBuilder extends ComputerBuilder {
private Computer computer;
public LenovoComputerBuilder(String cpu, String ram, String graphicsCard) {
computer = new Computer(cpu, ram, graphicsCard);
}
@Override
public void setDisplay() {
computer.setDisplay("联想显示器");
}
@Override
public void setKeyboard() {
computer.setKeyboard("联想键盘");
}
@Override
public Computer getComputer() {
return computer;
}
}
指挥者(Director)
public class ComputerDirector {
// 组装产品的方法
public void makeComputer(ComputerBuilder builder){
builder.setDisplay();
builder.setKeyboard();
}
}
客户端(Client)
public class Client {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
ComputerBuilder builder = new MacComputerBuilder("I5处理器","SDRAM", "集成显卡");
director.makeComputer(builder);
Computer macComputer = builder.getComputer();
System.out.println("mac computer:" + macComputer.toString());
ComputerBuilder lenovoBuilder = new LenovoComputerBuilder("I7处理器","RDRAM", "独立显卡");
director.makeComputer(lenovoBuilder);
Computer lenovoComputer = lenovoBuilder.getComputer();
System.out.println("lenovo computer:" + lenovoComputer.toString());
}
}
程序运行结果:
mac computer:Computer{cpu='I5处理器', ram='SDRAM', graphicsCard='集成显卡', display='苹果显示器', keyboard='苹果键盘'}
lenovo computer:Computer{cpu='I7处理器', ram='RDRAM', graphicsCard='独立显卡', display='联想显示器', keyboard='联想键盘'}
(2)、在实际开发过程中使用的建造者模式
实际在开发中,Director常常被忽略,而是直接通过Builder的方式链式组装,同时Builder是产品Product的静态内部类。
产品角色(Product),Builder是其静态内部类
// 计算机
public class Computer {
private String cpu;// CPU,必选
private String ram;// 内存,必选
private String graphicsCard;// 显卡,必选
private String display;// 显示器,可选
private String keyboard;// 键盘,可选
private Computer(Builder builder){
this.cpu = builder.cpu;
this.ram = builder.ram;
this.graphicsCard = builder.graphicsCard;
this.display = builder.display;
this.keyboard = builder.keyboard;
}
// Builder,静态内部类
public static class Builder{
private String cpu;// 必选
private String ram;// 必选
private String graphicsCard;// 必选
private String display;//可选
private String keyboard;//可选
public Builder(String cup, String ram, String graphicsCard){
this.cpu = cup;
this.ram = ram;
this.graphicsCard = graphicsCard;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
//省略getter方法
public Computer build(){
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", graphicsCard='" + graphicsCard + '\'' +
", display='" + display + '\'' +
", keyboard='" + keyboard + '\'' +
'}';
}
}
客户端(Client),使用链式调用,一步一步的把对象构建出来。
public class Client {
public static void main(String[] args) {
Computer macComputer=new Computer.Builder("I5处理器","SDRAM", "集成显卡")
.setDisplay("苹果显示器")
.setKeyboard("苹果键盘")
.build();
System.out.println("mac computer:" + macComputer.toString());
Computer lenovoComputer=new Computer.Builder("I7处理器","RDRAM", "独立显卡")
.setDisplay("联想显示器")
.setKeyboard("联想键盘")
.build();
System.out.println("lenovo computer:" + lenovoComputer.toString());
}
}
程序运行结果:
mac computer:Computer{cpu='I5处理器', ram='SDRAM', graphicsCard='集成显卡', display='苹果显示器', keyboard='苹果键盘'}
lenovo computer:Computer{cpu='I7处理器', ram='RDRAM', graphicsCard='独立显卡', display='联想显示器', keyboard='联想键盘'}