介绍
定义: 将一个复杂的对象与他的表示分离,使同样的构建过程可以创建不同的表示
主要作用: 在用户不知道创建过程和细节的情况下就可以直接创建复杂的对象。
何时使用: 一些基本部件不会变,而其组合经常变化的时候。
如何解决: 将变与不变分离开。
关键代码: 建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。
优点:
- 产品的建造和表示分离,实现了解耦 ,可以在用户不知道创建过程和细节的情况下就可以直接创建复杂的对象。
- 将复杂产品的创建过程分解在不同方法中,使创建过程更清晰。
- 具体的构建者类之间相互独立,有利于系统的扩展。增加新的具体建造者无需修改之前代码,符合开闭原则。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项: 与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。(工厂制造零件,建造者负责按顺序把零件组装。)
举例
我们用造房子为例,说明建造者模式。
造房子的大概步骤分为四步:地基–钢筋–铺电线–粉刷。无论最终房子是平房还是瓦房,楼房还是别墅,都是这四部。也就是同样的构建过程可以创建不同的表示。
但是实际上,我们不会自己造房子,我们会首先要找建筑公司或者包工头(Director指挥者),包工头按照图纸(抽象的Builder)去指挥工人(具体的Builder)盖房子(具体的Product)。也就是在用户不知道创建过程和细节的情况下就可以直接创建复杂的对象。
首先我们需要一个抽象的Builder,也就是图纸
//抽象的建造者:定义方法和接口
public abstract class Builder {
abstract void buildA();//地基
abstract void buildB();//钢筋
abstract void buildC();//铺电线
abstract void buildD();//粉刷
abstract Product getProduct();//完工:得到产品
}
还需要最后的产品房子,房子带有四个属性
//具体的产品:房子
public class Product {
private String buildA;//地基
private String buildB;//钢筋
private String buildC;//电线
private String buildD;//粉刷
@Override
public String toString() {
return "product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
}
抽象的Builder(图纸)需要由具体的Builder(工人)去实现,工人实现后就制造出了产品:房子
//具体的建造者:工人
public class Work extends Builder{
private Product product;//最后制造的房子
public Work() {
product=new Product();//房子是由工人建造出来的
}
@Override
void buildA() {
product.setBuildA("地基");
System.out.println("地基已经建好");
}
@Override
void buildB() {
product.setBuildB("钢筋");
System.out.println("钢筋已经建好");
}
@Override
void buildC() {
product.setBuildC("电线");
System.out.println("电线已经建好");
}
@Override
void buildD() {
product.setBuildD("粉刷");
System.out.println("粉刷已经建好");
}
@Override
Product getProduct() {
return product;//把工人建造的房子返回
}
}
但是实际上这个房子这个时候并没有做出来,因为没有人去指挥工人按顺序构建ABCD这四个任务。所以我们需要一个指挥者,去指挥工人建造房子,告诉他们任务顺序。
//指挥者:核心!指挥工程如何构建,顺序由他决定
public class Director {
//指挥者指挥工人按顺序建造房子
public Product build(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();//工人建好的房子交给指挥者判断是否完工
}
}
我们最后写一个测试类,模拟我们的需求
//测试类
public class Test {
public static void main(String[] args) {
//找到建筑公司
Director director=new Director();
//建筑公司指挥工人建造房子
Product product = director.build(new Work());
//得到房子
System.out.println(product.toString());
}
}
通过运行结果我们可以得知,我们的需求得到了满足,房子已经按顺序建好,但是我们并不知道具体是怎么建造的。我们只是找到了建筑公司(new一个指挥者)。
如果我们不想按照建筑公司的顺序去建造房子,想改变施工顺序,我们直接和建筑公司沟通(修改Director类里面方法执行顺序)就可以了,无需改变工人(Work)。
//指挥者:核心!指挥工程如何构建,顺序由他决定
public class Director {
//指挥者指挥工人按顺序建造房子
public Product build(Builder builder){
//修改顺序
builder.buildB();
builder.buildC();
builder.buildA();
builder.buildD();
return builder.getProduct();//工人建好的房子交给指挥者判断是否完工
}
}
在测试类不变的情况下,再一次运行可得,施工顺序已经改变。
但是有些时候,指挥者是不被需要的。比如我们去德克士点餐,我们(test)可以直接选择套餐内容或者单点,然后服务员(Builder)去组合,最后将我们点的食物(product)送给我们。
我们可以使用静态内部类去实现这种建造者模式,这种方式更灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容。并且无需改变具体的构造方式,就可以生产出不同复杂的产品。
静态内部类实现
先定义一个抽象的建造者和产品,在产品中有默认的顺序和种类。
//抽象的建造者
public abstract class Builder {
abstract Builder buildA(String msg);//默认汉堡
abstract Builder buildB(String msg);//默认薯条
abstract Builder buildC(String msg);//默认炸鸡
abstract Builder buildD(String msg);//默认可乐
abstract Product getProduct();//得到产品
}
//产品:套餐
public class Product {
//套餐默认是这些
private String buildA="汉堡";
private String buildB="薯条";
private String buildC="炸鸡";
private String buildD="可乐";
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
有了抽象的建造者,就可以写他的实现类,具体的建造者
//具体的建造者:服务员
public class Work extends Builder{
private Product product;
public Work() {
product=new Product();//具体的建造者去创建产品
}
@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildC(msg);
return this;
}
@Override
Builder buildD(String msg) {
product.setBuildD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
如果我们不更改套餐,就按照默认套餐,我们只需要和服务员(具体的建造者)说一下即可。
public class Test {
public static void main(String[] args) {
Work work=new Work();
Product product=work.getProduct();
System.out.println(product.toString());
}
}
如果我们想要更改套餐内容,直接和服务员(具体的建造者)沟通即可,不需要知道具体是怎么构建的。在用户不知道创建过程和细节的情况下就可以直接创建复杂的对象。
public class Test {
public static void main(String[] args) {
Work work=new Work();
Product product=work.buildA("鸡肉卷")
.buildD("雪碧")
.getProduct();
System.out.println(product.toString());
}
}
与原来相比,可以自由组合了,如果不组合也有默认套餐。同样的构建过程可以创建不同的表示
与抽象工厂模式对比
- 与抽象工厂相比,建造者模式返回一个组装好的完整产品。而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级,构成了一个产品族。
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象。在建造者模式中,客户端可以不直接调用建造者的相关方法,而通过指挥者类来知道如何生成对象,包括对象的组装过程和建造步骤,她侧重于一步步构造一个复杂对象,返回一个完整的对象。
- 如果抽象工厂模式看做一个汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装返回一辆完整的汽车。