【设计模式】建造者模式
建造者模式的理解
四个角色:
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.关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
这里我是参考了我们项目里的一个业务,简单说就是有一个任务表,里面可以存储多种类型的任务,不同类型的任务存储的字段以及值不同。里面共有20多个字段。现在暂定只有两种任务。打印任务,即打印pdf文件的任务。刻录任务,即刻录光盘的任务。
具体实现
-
创建一个任务的实体类,这个就很常见了,里面定义一些属性和方法
-
创建一个抽象建造者类,里面定义好多个抽象的方法,将来会将这些方法组合起来组成建造的流程。
-
创建两个具体的建造者类,一个是打印任务建造者,一个是刻录任务建造者,分别继承抽象建造者,并重写抽象方法。
-
创建一个指挥者类,里面可以定义通过构造器或者setter方法传入参数,最后定义一个具体的建造任务的流程方法。
-
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); } }
//Task(type=1, name=打印任务, status=1) //Task(type=2, name=刻录任务, status=1)
-
创建一个user类,里面定义三个属性用于演示,实际上user表可能有超过20个字段的可能。
-
创建一个建造者类,里面将user类改称为其的内部类,并将各个属性的构造步骤添加进去,每次完成一个步骤都返回this。
-
//定义一个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; }
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