Builder Pattern
1、概述
建造者解决的业务场景是:创建复杂对象,对象中可能包含了许多非简单类型属性,并且这样的对象具有重复性,每个对象的属性大致相同,只是其中具体的属性值不一样。如:游戏中人物模型,游戏人物都有脸型、肤色、服装、发型、装饰等属性,这些属性对于人物模型来说其实并非一个String能解决的,而且每种人物角色的属性值都不一样,这就需要使用建造者模式。
2、组成
- Product(产品角色):需要创建的对象,一般属性都比较复杂。
class Product {
private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
- Builder(抽象建造者):主要包含对Product中各部分属性的组建,还有一个返回Product的方法。
abstract class Builder {
//创建产品对象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
- ConcreteBuilder(具体建造者):抽象建造者的实现,具体某种对象属性的生成。
- Director(指挥者):调用Builder中属性生成方法,决定对象的生成顺序及方式。
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder=builder;
}
public void setBuilder(Builder builder) {
this.builder=builer;
}
//产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
3、实战
我们现在使用上述思路将概述中的游戏角色用建造者实现,为了方便阅读,产品角色属性使用String类型代替。
Product
首先定义我们的复杂角色对象
class Actor
{
private String type; //角色类型
private String sex; //性别
private String face; //脸型
private String costume; //服装
private String hairstyle; //发型
public void setType(String type) {
this.type = type;
}
public void setSex(String sex) {
this.sex = sex;
}
……
}
Builder
提供一个对象属性抽象构造类,其中包含对于每一个复杂属性的build方法和返回产品角色的方法。
注:这里使用抽象类没有使用接口,是因为其中需要包含一个有具体实现的方法:构造器createActor()。
//角色建造器:抽象建造者
abstract class ActorBuilder
{
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
//工厂方法,返回一个完整的游戏角色对象
public Actor createActor()
{
return actor;
}
}
ConcreteBuilder
根据不同的游戏角色类型实现该抽象类
//英雄角色建造器:具体建造者
class HeroBuilder extends ActorBuilder
{
public void buildType()
{
actor.setType("英雄");
}
public void buildSex()
{
actor.setSex("男");
}
public void buildFace()
{
actor.setFace("英俊");
}
public void buildCostume()
{
actor.setCostume("盔甲");
}
public void buildHairstyle()
{
actor.setHairstyle("飘逸");
}
}
//恶魔角色建造器:具体建造者
class DevilBuilder extends ActorBuilder
{
public void buildType()
{
actor.setType("恶魔");
}
public void buildSex()
{
actor.setSex("妖");
}
public void buildFace()
{
actor.setFace("丑陋");
}
public void buildCostume()
{
actor.setCostume("黑衣");
}
public void buildHairstyle()
{
actor.setHairstyle("光头");
}
}
Director
提供一个控制器,以此控制角色属性生成的顺序
//游戏角色创建控制器:指挥者
class ActorController
{
//逐步构建复杂产品对象
public Actor construct(ActorBuilder ab)
{
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
actor=ab.createActor();
return actor;
}
}
考虑到代码的灵活性和可扩展性,我们将具体建造者的类目放在配置文件中,这样通过修改配置文件便可简单变更产品对象。如果需要增加新角色,可以增加一个新的具体角色建造者类作为抽象角色建造者的子类,再修改配置文件即可,原有代码无须修改,完全符合“开闭原则”。
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
class XMLUtil
{
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getBean()
{
try
{
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config.xml"));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
配置文件如下
<?xml version="1.0"?>
<config>
<className>AngelBuilder</className>
</config>
这样我们的游戏角色建造者模式就开发好了,我们可以书写测试类测试
class Client
{
public static void main(String args[])
{
ActorBuilder ab; //针对抽象建造者编程
ab = (ActorBuilder)XMLUtil.getBean(); //反射生成具体建造者对象
ActorController ac = new ActorController();
Actor actor;
actor = ac.construct(ab); //通过指挥者创建完整的建造者对象
String type = actor.getType();
System.out.println(type + "的外观:");
System.out.println("性别:" + actor.getSex());
System.out.println("面容:" + actor.getFace());
System.out.println("服装:" + actor.getCostume());
System.out.println("发型:" + actor.getHairstyle());
}
}
结果为:
天使的外观:
性别:女
面容:漂亮
服装:白裙
发型:披肩长发
4、优化
有的同学可能会问,Director是不是必须的,答案当然是否定的,引入Director符合"单一职责原则",Product只负责对象是什么样子,Builder专注于对象各组成部分细节上如何构建,而Director管理的是几个组成部分之间如何组合。如果业务需求不十分复杂并且以后新增的情况也较少,我们完全可以把Director抽离出来,将此部分功能放到Builder里去做。
abstract class ActorBuilder
{
protected static Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public static Actor construct(ActorBuilder ab)
{
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
return actor;
}
}
甚至说我们不用指定construct的入参,直接使用this指代更加简单
abstract class ActorBuilder
{
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public Actor construct()
{
this.buildType();
this.buildSex();
this.buildFace();
this.buildCostume();
this.buildHairstyle();
return actor;
}
}
5、精确控制
对于上述方法我们已经实现了不同角色之间没有耦合,新增、修改角色对原系统也无影响,已经符合建造者模式,但如果我们还想进一步精确控制对象各部分是否创建,可以引入HookMethod(钩子方法)确定某个属性是否创建。
直接在抽象建造者中添加该方法,如果子类产品对象中有特殊需求覆盖即可。
isBareheaded()方法用于判断该角色是否是"光头",默认为false;
abstract class ActorBuilder
{
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
//钩子方法
public boolean isBareheaded()
{
return false;
}
public Actor createActor()
{
return actor;
}
}
我们的恶魔角色很明显是光头,所以需要覆盖该方法。
class DevilBuilder extends ActorBuilder
{
public void buildType()
{
actor.setType("恶魔");
}
public void buildSex()
{
actor.setSex("妖");
}
public void buildFace()
{
actor.setFace("丑陋");
}
public void buildCostume()
{
actor.setCostume("黑衣");
}
public void buildHairstyle()
{
actor.setHairstyle("光头");
}
//覆盖钩子方法
public boolean isBareheaded()
{
return true;
}
}
这样我们的指挥着也需要进行些微改动
class ActorController
{
public Actor construct(ActorBuilder ab)
{
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
//通过钩子方法来控制产品的构建
if(!ab.isBareheaded())
{
ab. buildHairstyle();
}
actor=ab.createActor();
return actor;
}
}
6、总结
其实建造者模式和抽象工厂模式有些类似,但是建造者模式关注的是复杂对象的创建,而抽象工厂则是返回一系列产品。抽象工厂模式只能生产特定的运动器材,如Adidas只能生产Adidas的球衣、球鞋,Nike只能生产Nike的球衣、球鞋,而建造者模式就好比服装厂商,我可以让服装产商定制一套队服,包括球衣(大小)、球鞋(尺码)、球袜等。参考
建造者模式所有函数加到一起才能生成一个对象,抽象工厂一个函数就能生成一个对象
①优点
- 客户端与产品的创建过程解耦,使用相同的创建过程即可创建不同的产品对象。
- 每个对象相互独立、互不影响,变更对象只需新增实现、修改配置文件即可,符合"开闭原则"。
- 更加精细化的控制产品创建,控制粒度更细。
②缺点
- 建造者模式的产品属性基本一致,只是属性间差异较大,如果某个对象大部分属性都不相同,则不适合建造者模式。
- 如果业务需求复杂庞大,可能需要定义N多个具体建造者类,将增加系统的难度及运维成本。
③适用场景
- 产品对象内部结构复杂包含多个成员属性。
- 产品对象属性互相依赖,需要指定其生成顺序。
- 需要使用相同创建过程创建不同产品。