Java设计模式-建造者模式(Builder Pattern)
目录
- 什么是建造者模式
- 建造者模式的实现
- JavaSE中建造者模式的使用
- Struts2建造者模式的应用
工厂模式关注的是构建结果,一个工厂生产一类对象;而建造者模式关注的是构建过程,调用不同的方法生产不同的对象。
一、什么是建造者模式
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
建造者模式一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
UML表示图如下
建造者模式主要包含4个角色:
- 抽象建造者(Builder):要构建的目标对象所需要的“零部件”,大多是接口
- 具体建造者(ConcreteBuilder):Builder的实现类,提供创建方法以及产品实例
- 产品类(Product):要构建的对象,内部包含很多其他的“零部件对象”
- 指挥者类(Director):负责调用Builder的方法构建对象,返回实例,对调用者屏蔽了具体的构建过程,构建者一般只与指挥者类打交道
二、建造者模式的实现
还是举一个造车的例子,一辆汽车需要窗户、座椅、轮胎等,而每个零部件都会有单独的厂商,汽车生产厂家不过是使用零部件组装为一辆汽车。
该例子包含的4个角色:
- 抽象建造者(Builder):ICarBuilder
- 具体建造者(ConcreteBuilder):CarBuilder
- 产品类(Product):Car
- 指挥者类(Director):CarDirector
代码如下
package org.builderPattern.version1;
/**
* 产品类
*/
public class Car {
private String window;
private String tyre;
private String seat;
private boolean ai;
public boolean getAi() {
return ai;
}
public void setAi(boolean ai) {
this.ai = ai;
}
public String getWindow() {
return window;
}
public void setWindow(String window) {
this.window = window;
}
public String getTyre() {
return tyre;
}
public void setTyre(String tyre) {
this.tyre = tyre;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
@Override
public String toString() {
String rs = "这款汽车有:" + this.getSeat() + "、" + this.getTyre() + "、" + this.getWindow();
if(this.getAi()){
rs += "、自动驾驶";
}
return rs;
}
}
package org.builderPattern.version1;
/**
* 抽象建造者
*/
public interface ICarBuilder {
public ICarBuilder setWindows(String window);
public ICarBuilder setSeat(String seat);
public ICarBuilder setTyre(String tyre);
public ICarBuilder setAI(boolean ai);
public Car createCar();
}
package org.builderPattern.version1;
/**
* 具体建造者
*/
public class CarBuilder implements ICarBuilder{
private Car car = new Car();
@Override
public ICarBuilder setWindows(String window) {
this.car.setWindow(window);
System.out.println("给汽车安装[" + window + "]");
return this;
}
@Override
public ICarBuilder setSeat(String seat) {
this.car.setSeat(seat);
System.out.println("给汽车安装[" + seat + "]");
return this;
}
@Override
public ICarBuilder setTyre(String tyre) {
this.car.setTyre(tyre);
System.out.println("给汽车安装[" + tyre + "]");
return this;
}
@Override
public ICarBuilder setAI(boolean ai) {
this.car.setAi(ai);
if (ai){
System.out.println("给汽车安装[自动驾驶]");
}
return this;
}
@Override
public Car createCar() {
return this.car;
}
}
package org.builderPattern.version1;
/**
* 指挥者
*/
public class CarDirector {
public Car createCar(ICarBuilder carBuilder){
carBuilder.setSeat("加热座椅")
.setTyre("耐磨轮胎")
.setWindows("普通窗户");
Car car = carBuilder.createCar();
return car;
}
public Car createAICar(ICarBuilder carBuilder){
carBuilder.setSeat("加热座椅")
.setTyre("耐磨轮胎")
.setWindows("普通窗户")
.setAI(true);
Car car = carBuilder.createCar();
return car;
}
}
package org.builderPattern.version1;
public class Client {
public static void main(String[] args) {
CarDirector director = new CarDirector();
Car car = director.createCar(new CarBuilder());
System.out.println(car.toString());
System.out.println("---------------------------------");
Car aiCar = director.createAICar(new CarBuilder());
System.out.println(aiCar);
}
}
// 运行结果
给汽车安装[加热座椅]
给汽车安装[耐磨轮胎]
给汽车安装[普通窗户]
这款汽车有:加热座椅、耐磨轮胎、普通窗户
---------------------------------
给汽车安装[加热座椅]
给汽车安装[耐磨轮胎]
给汽车安装[普通窗户]
给汽车安装[自动驾驶]
这款汽车有:加热座椅、耐磨轮胎、普通窗户、自动驾驶
使用IDEA的Diagrams功能生成的类图(在package上右键,选择Diagrams即可)
案例中Client使用CarDirector指挥CarBuilder创造Car对象,Client并没有直接接触到CarBuilder类,这符合**迪米特法则:只和朋友交流,不和陌生人说话,实现解耦
**。
在创建的两个Car中,一个有自动驾驶一个没有,CarBuilder通过链式调用精准的控制Car类创建的过程,将复杂的创建步骤,分解到不同的方法中,使创建过程更加清晰。总结下建造者模式的优缺点:
优点:
- 解耦,调用者无需知道构建细节,将产品本身与创建过程解耦,相同的创建过程可以创建不同的产品对象
- 灵活,指挥者只调用建造者建造产品,新增新的建造者无需修改原有类库代码,扩展方便,符合开闭原则,一般产品类和建造者是比较稳定的。
- 精细,复杂的创建过程拆解为不同方法,灵活调用,实现组装
缺点:
- 建造者模式所创建的产品一般具有较多的额共同点,如果产品间差别很大时不适用,可以考虑工厂模式
- 如果产品过于复杂,会导致定义过多的建造者类来实现这类变化,导致系统庞大
适用场景:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
三、JavaSE中建造者模式的使用
3.1、java.lang.StringBuilder
分析下StringBuilder包含的4个角色:
- 抽象建造者(Builder):AbstractStringBuilder、Appendable
- 具体建造者(ConcreteBuilder):StringBuilder
- 产品类(Product):char数组,AbstractStringBuilder类的char[] value;
- 指挥者类(Director):无
Appendable接口定义了字符串组装的方法,由AbstractStringBuilder、StringBuilder进行实现具体的字符串组装方法,组装的产品就是字符串,其实本质上是char数组。
3.2、java.lang.StringBuffer
在上学的时候老师总是教给我们,能用StringBuffer就用StringBuffer,但为什么?老师没讲,接下来我们分析下
可以发现StringBuffer同样是AbstractStringBuilder、Appendable的子类,看一下它的append方法
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
发现了synchronized关键字,这是线程安全的,所以老师说能用StringBuffer就用StringBuffer
四、Struts2建造者模式的应用
在Struts2的ContainerBuiler类使用了建造者模式,类图如下
分析下ContainerBuiler包含的4个角色:
- 抽象建造者(Builder):无
- 具体建造者(ConcreteBuilder):ContainerBuilder
- 产品类(Product):ContainerImpl(Container的子类)
- 指挥者类(Director):无
这个类比较特殊,需要先了解Struts2的容器加载机制,产品类ContainerImpl需要factories,该factories在ContainerBuiler进行创建。
class ContainerImpl implements Container {
final Map<Key<?>, InternalFactory<?>> factories;
}
在ContainerBuiler中通过多个factory重载方法完成对factories列表的构造,factories中存放的实际是对象的创建工厂InternalFactory。
当ContainerBuiler调用create方法时,方法内部将factories封装到Container对象,从而返回一个Container对象。ContainerBuiler由此完成了对Container对象的构建。
public Container create(boolean loadSingletons) {
ensureNotCreated();
created = true;
// 将factories封装到ContainerImpl中
final ContainerImpl container = new ContainerImpl(
new HashMap<Key<?>, InternalFactory<?>>(factories));
if (loadSingletons) {
container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
public Void call(InternalContext context) {
for (InternalFactory<?> factory : singletonFactories) {
factory.create(context);
}
return null;
}
});
}
container.injectStatics(staticInjections);
return container;
}
关于Struts2的容器加载机制,可以浏览《Mark链接-Struts2依赖注入实现原理》