建造者模式的理解

  定义: 官方的说法是,将一个复杂的对象的构建与它的表示分离,即隐藏了复杂对象的创建过程,把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

 

四个角色:

   1.产品角色(Product):最终要生成的对象实例

   2.抽象建造者(Builder):构建者的抽象基类(也可用接口代替),里面定义了构建product的步骤。通常还包含一个返回复杂产品的方法 getResult()。

   3.具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。

   4.指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

 

具体使用

   1.首先生成一个director

   2.然后生成一个目标builder

   3.接着使用director组装builder

   4.组装完毕后使用builder创建产品实例

 

大白话理解:

   比如现在有5个产品,他们的参数全部或者大部分是固定的,比如颜色、大小、重量等,那么我们就需要写死在一个地方,这就可以用到建造者模式,这样每次直接创建就可以按照如下顺序直接创建出来了。先创建director、再创建builder、接着使用director组装builder、组装完毕后使用builder创建产品实例

 

什么时候可以使用

   1.当一个类里面的属性过多的时候,建议使用

   2.当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式

 

建造者模式主要适用于以下应用场景:

   1.相同的方法,不同的执行顺序,产生不同的结果。

   2.多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。

   3.产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。

   4.初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

 

优点

   1.使用建造者模式可以使客户端不必知道产品内部组成的细节。

   2.具体的建造者类之间是相互独立的,这有利于系统的扩展。

   3.具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。

 

缺点

   1.建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

   2.如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

   3.如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。

 

建造者模式和工厂模式的区别

   1.建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。

   2.创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样

   3.关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。

   4.建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。

 

传统方式的建造者模式

背景介绍

这里我是参考了我们项目里的一个业务,简单说就是有一个任务表,里面可以存储多种类型的任务,不同类型的任务存储的字段以及值不同。里面共有20多个字段。现在暂定只有两种任务。打印任务,即打印pdf文件的任务。刻录任务,即刻录光盘的任务。

具体实现

  1. 创建一个任务的实体类,这个就很常见了,里面定义一些属性和方法
  2. 创建一个抽象建造者类,里面定义好多个抽象的方法,将来会将这些方法组合起来组成建造的流程。
  3. 创建两个具体的建造者类,一个是打印任务建造者,一个是刻录任务建造者,分别继承抽象建造者,并重写抽象方法。
  4. 创建一个指挥者类,里面可以定义通过构造器或者setter方法传入参数,最后定义一个具体的建造任务的流程方法。
  5. 最后创建一个测试类,可以通过传入不同的具体建造者类,来创建出不同的任务类。


【设计模式】建造者模式_ide【设计模式】建造者模式_建造者模式_02


import lombok.Data;

//首先定义一个Product产品类
//定义一些属性和方法
@Data
public class Task {

//任务类别 1:打印 2:刻录
private Integer type;

//任务名称
private String name;

//使用状态 1:使用 0:禁用
private Integer status;

//省略其他字段

//这里可以定义一些Task类的方法,随便举个例子,比如获取task任务的信息
public void getTaskMsg(){
System.out.println("当前任务的信息为:Type:"+type+" Name:"+name+" Status"+status);
}

}

//定义一个抽象建造者类
//里面可以自定义多个抽象方法,即这些抽象的方法组合起来就是建造的流程
public abstract class TaskBuilder {

protected Task task = new Task();

//抽象方法-->建造的流程
public abstract void buildType();

public abstract void buildName();

public abstract void buildStatus();

//建造任务,然后将产品(任务)return
public Task buildTask() {
return task;
}

}

//接下来新建两个具体的建造者对象,分别代表打印任务和刻录任务,并让他们继承抽象继承者
public class PrintTask extends TaskBuilder {

//这里可以传参,也可以不传。具体逻辑自己定义,返回值也可以自己定义。
//可以通过返回具体建造者类来实现链式调用。
@Override
public void buildType() {
task.setType(1);
}

@Override
public void buildName() {
task.setName("打印任务");
}

@Override
public void buildStatus() {
task.setStatus(1);
}
}

//具体含义可以参考PrintTask建造者
public class BurnTask extends TaskBuilder {
@Override
public void buildType() {
task.setType(2);
}

@Override
public void buildName() {
task.setName("刻录任务");
}

@Override
public void buildStatus() {
task.setStatus(1);
}
}

//指挥者,在这里可以动态的去指定制作流程,返回产品
public class TaskDirector {

TaskBuilder taskBuilder = null;
//下面可以看出,可以通过构造器或者setter方式实现赋值
//构造器传入 taskBuilder
public TaskDirector(TaskBuilder taskBuilder) {
this.taskBuilder = taskBuilder;
}
//通过setter传入taskBuilder
public void setTaskBuilder(TaskBuilder taskBuilder) {
this.taskBuilder = taskBuilder;
}
//如何处理建造任务的流程,交给指挥者。
public Task createTask() {
taskBuilder.buildName();
taskBuilder.buildStatus();
taskBuilder.buildType();
return taskBuilder.buildTask();
}

}

//测试类
public class Test {

public static void main(String[] args) {
//创建打印任务
TaskDirector taskDirector1 = new TaskDirector(new PrintTask());
Task printTask = taskDirector1.createTask();
System.out.println(printTask);

//创建刻录任务
TaskDirector taskDirector2 = new TaskDirector(new BurnTask());
Task burnTask = taskDirector2.createTask();
System.out.println(burnTask);
}

}

View Code

 

结果:



//Task(type=1, name=打印任务, status=1)
//Task(type=2, name=刻录任务, status=1)


 

简化后的建造者模式

步骤如下:

  1. 创建一个user类,里面定义三个属性用于演示,实际上user表可能有超过20个字段的可能。
  2. 创建一个建造者类,里面将user类改称为其的内部类,并将各个属性的构造步骤添加进去,每次完成一个步骤都返回this。
  3. 创建一个测试类,使用建造者方式的链式写法和传统方式两种方法分别创建user对象,并设置属性值,可以看到建造者方法,代码更简洁,逻辑更清楚,且user对象的属性越多,优点越明显。



//定义一个user对象,并定义三个属性,类上加上Data注解,省略了set get方法
@Data
public class User {
private String name;

private Integer age;

private String sex;
}

//创建建造者类
public class UserBuilder {
//将User类改成为UserBuilder类的内部类,并将构造步骤添加进来,每次完成一个步骤都返回this
private User user = new User();

public UserBuilder addName(String name) {
this.user.setName(name);
return this;
}

public UserBuilder addAge(Integer age) {
this.user.setAge(age);
return this;
}

public UserBuilder addSex(String sex) {
this.user.setSex(sex);
return this;
}

public User builder() {
return this.user;
}
}

//创建测试类
public class Test {
public static void main(String[] args) {
//下面使用了两种方法来创建了user对象,那么可以看出方法1使用了建造者模式的链式写法,
//代码更简洁,逻辑更清楚。且user对象的属性越多,优点越明显

//方法1
UserBuilder userBuilder = new UserBuilder();
userBuilder.addAge(11).addName("张三").addSex("男");
User user = userBuilder.builder();
System.out.println("建造者方法生成用户为:"+user);

//方法2
User user2 = new User();
user2.setSex("女");
user2.setName("小丽");
user2.setAge(11);
System.out.println("普通方法生成对象:"+user2);
}
}


 

 

应用

  我们在开发过程中常见的StringBuilder其实就用到了建造者模式,并使用了链式调用,如下:



public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
builder.append("1").append("2").append("3");
System.out.println(builder);
}


 

分析

  首先看他的部分源码

  总结:

    1.Appendable接口定义了多个append方法(抽象方法),即Appendable为抽象建造者,定义了抽象方法。

    2.AbstractStringBuilder实现了Appendable接口。所以他是建造者,虽然他是一个抽象类,不能实例化。

    3.StringBuilder类继承AbstractStringBuilder,对于它来说,它既充当了指挥者角色,同时充当了具体的建造者。建造方法的具体实现是由AbstractStringBuilder完成


【设计模式】建造者模式_ide【设计模式】建造者模式_建造者模式_02


//StringBuilder
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{

static final long serialVersionUID = 4383685877147921099L;

@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}

@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

public java.lang.StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}

@Override
public java.lang.StringBuilder append(CharSequence s) {
super.append(s);
return this;
}

}

//AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {}


//Appendable
public interface Appendable {
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
}

View Code