【设计模式】建造者模式

建造者模式的理解

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

 

四个角色:

   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. 最后创建一个测试类,可以通过传入不同的具体建造者类,来创建出不同的任务类。

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完成

//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

 

 

参考:

https://www.jianshu.com/p/3d1c9ffb0a28
https://zhuanlan.zhihu.com/p/58093669
http://c.biancheng.net/view/1354.html
https://blog.csdn.net/Woo_home/article/details/104362776
https://blog.csdn.net/qq_42339210/article/details/106742211

 

 

posted @ 2021-08-28 10:02  夏夜凉凉  阅读(214)  评论(0编辑  收藏  举报