一、认识建造者模式
建造者模式:是创建型模式的一种,与工厂模式比较,工厂模式主要是负责创建某个零件,建造者模式则是负责具体的操作,也可以说是负责组装。通常建造者模式与工厂模式一起搭配使用!
主要角色:
产品角色(Product):其包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
抽象建造者(Builder):为其包含多个创建产品各个子部件的抽象方法接口,并包含一个返回产品的接口。
具体建造者(Concrete Builder):实现Builder抽象类,并且实现完成各个负责操作的接口。
指挥者(Director):主要是用来指挥建造者对象,来完成复杂产品对象的创建,在指挥者中不会涉及产品信息,只负责指挥建造者。
目的:其实也是为了创建对象,只不过在建造者模式中你是通过具体建造者来去创建复杂对象,你可以自己去指挥建造者做事也可以创建出一个指挥者来替你做事(例如你想要个产品,你去找指挥者,指挥者指挥建造者去创建产品,并将建造者创建的产品提交给你,注意了指挥者并不会涉及产品操作仅作指挥)。
优缺点:
优点:封装性好,构建和表示分离;扩展性好,各个具体的建造者相互独立,利于系统的解耦;隐藏内部组成细节,建造者可以对创建过程进行逐步细化。
缺点:若是产品内部发生改变,建造者也要进行同步修改,维护成本大,违反了开闭原则。
建造者模式UML图:
二、实现建造者模式
2.1、完整实现建造者模式(含指挥者)
实现过程
例子见builder包中的demo1:
通过指挥者来指挥建造者要做的事情并且将建造者创建的产品返回给顾客。
具体产品:Product
public class Product { private String builderA; private String builderB; private String builderC; private String builderD; public String getBuilderA() { return builderA; } public void setBuilderA(String builderA) { this.builderA = builderA; } public String getBuilderB() { return builderB; } public void setBuilderB(String builderB) { this.builderB = builderB; } public String getBuilderC() { return builderC; } public void setBuilderC(String builderC) { this.builderC = builderC; } public String getBuilderD() { return builderD; } public void setBuilderD(String builderD) { this.builderD = builderD; } @Override public String toString() { return "Product{" + "builderA='" + builderA + '\'' + ", builderB='" + builderB + '\'' + ", builderC='" + builderC + '\'' + ", builderD='" + builderD + '\'' + '}'; } }
抽象建造者:Builder
//抽象工人类 public abstract class Builder { //各个具体的抽象步骤 abstract void builderA(); abstract void builderB(); abstract void builderC(); abstract void builderD(); //产生产品 abstract Product getProduct(); }
具体建造者:Worker
//具体建造者:负责实现各个步骤以及产品提供 public class Worker extends Builder{ //需要获取一个产品 private Product product; public Worker(){ this.product = new Product(); } @Override void builderA() { product.setBuilderA("打地基"); System.out.println("打地基"); } @Override void builderB() { product.setBuilderB("拉线"); System.out.println("拉线"); } @Override void builderC() { product.setBuilderC("搭房子"); System.out.println("搭房子"); } @Override void builderD() { product.setBuilderB("砌墙"); System.out.println("砌墙"); } @Override Product getProduct() { return product; } }
指挥者:Director
//指挥者:主要来指挥具体建造者做事 public class Director { //通过传入参数的建造者,可以指挥建造者做事的顺序,并返回建造者创建的产品 public static Product build(Builder builder){ builder.builderA(); builder.builderB(); builder.builderC(); builder.builderD(); return builder.getProduct(); } }
顾客类(即测试类):Customer
//顾客类(测试) public class Customer { public static void main(String[] args) { //创建一个指挥者 Director director = new Director(); //调用建造方法,传入参数为指定的建造者 Product product = director.build(new Worker()); //打印一下产品信息 System.out.println(product); } }
说明:具体产品包含set/get方法包含属性,具体建造者实现抽象建造者的抽象方法来完成产品的各个复杂步骤,对于指挥者则是去指挥建造者完成产品的步骤顺序,其本身是不会接触到产品,并负责将建造者创建出的产品交接给用户!
2.2、不含指挥者的建造者模式
该demo不通过指挥者来控制建造过程,而是交给客户端来进行指挥建造者的产品创建!
见builder包中的demo2:
该demo并没有指挥者存在,我们可根据自己的需求来让建造者创建出产品出来。
具体产品:Product
//具体产品:一种搭配 public class Product { private String fruit = "葡萄"; private String drink = "牛奶"; private String food = "意面"; private String tool = "手套"; public String getFruit() { return fruit; } public void setFruit(String fruit) { this.fruit = fruit; } public String getDrink() { return drink; } public void setDrink(String drink) { this.drink = drink; } public String getFood() { return food; } public void setFood(String food) { this.food = food; } public String getTool() { return tool; } public void setTool(String tool) { this.tool = tool; } @Override public String toString() { return "Product{" + "fruit='" + fruit + '\'' + ", drink='" + drink + '\'' + ", food='" + food + '\'' + ", tool='" + tool + '\'' + '}'; } }
本部分产品本身就有一组搭配,若我们想要替换可指挥具体建造者来进行更换操作。
抽象建造者:Builder
//具体工人 public abstract class Builder { abstract Builder buildFruit(String msg); abstract Builder buildDrink(String msg); abstract Builder buildFood(String msg); abstract Builder buildTool(String msg); abstract Product getProduct(); }
注意在这里其可以进行传参,并且返回本身对象用于链式操作!
具体建造者:Worker
public class Worker extends Builder { private Product product; public Worker() { this.product = new Product(); } @Override Builder buildFruit(String msg) { product.setFruit(msg); return this; } @Override Builder buildDrink(String msg) { product.setDrink(msg); return this; } @Override Builder buildFood(String msg) { product.setFood(msg); return this; } @Override Builder buildTool(String msg) { product.setTool(msg); return this; } @Override Product getProduct() { return product; } }
顾客类(即测试类):Customer
public class Customer { public static void main(String[] args) { Worker worker = new Worker(); Product product = worker.buildDrink("可乐") .getProduct(); System.out.println(product); } }
三、简易版建造者(简化set方法)
3.1、手写静态内部类(建造者)
给一个对象的属性赋初值常用方法:
我们以前给对象进行赋值通常都是通过set方法,若是赋值多个属性就要使用多个set方法,一旦属性过多那么set也就会越来越多造成代码量冗余;
通过有参构造,只不过这种方式你需要重载多个构造器,但这种形式虽说会在调用创建时很简短,但是阅读起来很不友好,你需要去看每个位置后的参数是什么,并且很容易出错。
学习了建造者模式之后,我们可以通过使用建造者链来简化set方法,起初我看到时也不禁直呼秒呀!
代码见builder包中的demo3:
public class Product { private int id; private String name; private int age; public Product(Builder builder){ this.id = builder.id; this.name = builder.name; this.age = builder.age; } //静态内部类:具体建造者 public static class Builder{ private int id; private String name; private int age; //有参构造限制写入id public Builder(int id){ this.id = id; } //set方法返回Builder实例,为形成链式操作 public Builder setName(int id){ this.id = id; return this; } public Builder setId(String name){ this.name = name; return this; } public Builder setAge(int age){ this.age = age; return this; } //builder()方法相当于创建一个Product实例,其中包含赋值操作 public Product builder(){ return new Product(this); } } @Override public String toString() { return "Product{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
我们测试一下,看一下使用了建造者链起到了什么样的效果:
//测试类 class Test{ public static void main(String[] args) { //通过链式set方法来进行赋值,最终使用builder()方法来创建出产品 Product builder = new Product.Builder(15).setAge(100) .builder(); System.out.println(builder); } }
说明:通过这种方式来构造对象是不是感觉一下子舒服了,以后一定会使用到实际项目中去。其实我们可以直接使用Lombok插件,只需要在类上添加一个@Builder就可以实现建造者模式的domain了!
3.2、使用lombok来实现简易建造者
引入loback的jar包,使用@Builder
需要引入lombok的jar包:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency>
实现过程
编写Domain类:Person
@Builder public class Person { private String name; private int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
看下IDEA的目录(这需要你在IDEA中安装lombok插件才能看见)
测试一下:
方式一:在类内部或者同一个包中使用方法(测试中使用)
class Test{ public static void main(String[] args) { //允许手动new其静态类 Person build = new Person.PersonBuilder() .age(18) .name("changlu") .build(); System.out.println(build); } }
方式二:在其他包中使用方法(实际项目中使用)
class Main2{ public static void main(String[] args) { //通过使用Person类的静态方法builder()获取一个内部静态实例 Person person = Person.builder() .age(18) .name("changlu") .build(); System.out.println(person); } }
看下反编译之后的代码
public class Person { private String name; private int age; public String toString() { return "Person{name='" + this.name + '\'' + ", age=" + this.age + '}'; } Person(String name, int age) { this.name = name; this.age = age; } public static Person.PersonBuilder builder() { return new Person.PersonBuilder(); } public static class PersonBuilder { private String name; private int age; //default方法,只允许在类内部,同一个包类进行new方法构造 PersonBuilder() { } public Person.PersonBuilder name(String name) { this.name = name; return this; } public Person.PersonBuilder age(int age) { this.age = age; return this; } public Person build() { return new Person(this.name, this.age); } public String toString() { return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ")"; } } }
说明:获取内部静态类PersonBuilder实例有两种方式:①new Person.PersonBuilder();②Person.builder()。获取好实例之后进行赋值操作,最终调用静态类的build()方法获取Person实例。
注意:静态内部类PersonBuilder的无参构造器是default方法,若是在不同包中建议使用第二种获取方式获取,否则会出现编译错误,权限不足无法构造。
总结
1、建造者模式也属于创建型模式,相对于工厂模式,工厂模式主要是负责创建某个零件,建造者模式则是负责具体的操作,将构建一个负责的对象的各个步骤交由一个建造者来实现,具体构建过程是交由一个指挥者指挥。
2、建造者模式的最终目的实际上也是产生出一个产品,交由指挥者去指挥建造者(可任意调整建造顺序)来创建产品给使用者或者说是用户。
3、建造者优点是封装性好,客户端不需要知道产品内部组成过程,其中建造者可以将创建过程细化,而不对其他模块产生任何影响;缺点是组成部分必须相同,若是产品内部变化建造者也要同步修改,后期维护成本较大,也违反了开闭原则。