设计模式学习笔记(八):建造者模式
1 建造者模式
1.1 定义
建造者模式:将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一种对象创建型模式。
建造者模式是较为复杂的创建型模式,它将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需的建造者类型即可。建造者模式关注一步一步地创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便,无须修改已有代码,系统具有较好的扩展性。
1.2 复杂对象
建造者模式中用到了复杂对象这个概念。
复杂对象就是指那些包含多个成员变量的对象,这些成员变量也叫部件或者零件,例如汽车包括方向盘,发动机,轮胎等 ,
汽车就是复杂对象,方向盘,发动机以及轮胎就是汽车的部件。
1.3 结构图
1.4 角色
建造者模式包含以下四个角色:
Builder
(抽象建造者):为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类是buildXXX()
方法,用于创建复杂对象的各个部分(部件),另一类是是getResult()
,用于返回复杂对象。Builder
既可以是抽象类,也可以是接口ConcreteBuilder
(具体建造者):实现了Builder
接口或者继承了Builder
类,实现各个部件的具体构造和装配方法,定义并明确其所创建的复杂对象,也可以提供一个方法返回创建好的复杂对象Product
(产品角色):是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义其装配过程Director
(指挥者):指挥者又叫导演类,复杂安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()
建造方法中调用建造者对象的部件构造以及装配方法,完成复杂对象的建造。客户端一般只需要与导演类进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象,然后通过导演类的构造函数或者setter将该对象传入导演类中
1.4.1 Product
一般来说Product
是一个复杂对象,典型的定义如下:
classProduct{private type1 part1;
private type2 part2;
private type3 part3;
//getter + setter ...
}
其中type1
,type2
等指各种不同的类型,一般来说会有嵌套类。
1.4.2 Builder
抽象建造者的典型定义如下:
abstractclassBuilder{protected Product product = new Product();
publicabstractvoidbuildPart1();
publicabstractvoidbuildPart2();
publicabstractvoidbuildPart3();
public Product getResult()
{
return product;
}
}
抽象建造者中声明了一系列buildXXX
方法,用于创建Product
的各个部件,具体创建过程在ConcreteBuilder
中实现,getResult()
返回已创建完成的Product
。
1.4.3 ConcreteBuilder
ConcreteBuilder
实现了Builder
中的buildXXX
方法,通过调用Product
的setter来实现给产品对象的各部分赋值。
不同的ConcreteBuilder
在实现buildXXX
是将有所区别,比如传入Product
的setter参数的不同。
另外在有些ConcreteBuilder
中某些buildXXX
无须实现(提供一个空实现),这些对客户端来说无须关心,客户端只需要知道具体建造者的类型即可。
典型定义如下:
classConcreteBuilderextendsBuilder{publicvoidbuildPart1()
{
product.setPart1("part1");
}
publicvoidbuildPart2()
{
product.setPart2("part2");
}
publicvoidbuildPart3()
{
product.setPart3("part3");
}
public Product getResult()
{
return product;
}
}
1.4.4 Director
Director
类主要有两个作用:
- 隔离了客户与创建过程
- 控制产品的创建过程,包括某个
buildXXX
方法是否被调用,以及调用时的先后次序等等
指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者调用建造者的相关方法,返回一个完整的产品对象。典型实现如下:
classDirector{private Builder builder;
publicDirector(Builder builder)
{
this.builder = builder;
}
publicvoidsetBuilder(Builder builder)
{
this.builder = builder;
}
public Product construct()
{
builder.buildPart1();
builder.buildPart2();
builder.buildPart3();
return builder.getResult();
}
}
2 实例
游戏角色的创建:不同的角色具有差别极大的外部特征,而且要求随着游戏的进行会不断出现新的角色,也就是说扩展性要好。这里例子简化就创建三个角色:英雄,天使与恶魔。
思路:
Actor
为复杂产品ActorController
为指挥者ActorBuilder
为抽象建造者HeroBuilder
/AngelBuilder
/DevilBuilder
为具体建造者
// 复杂产品classActor
{
private String type;
private String face;
private String costume;
private String hairstyle;
//getter and setter ...
}
//抽象建造者
abstractclassActorBuilder
{
protected Actor actor = new Actor();
publicabstractvoidbuildType();
publicabstractvoidbuildFace();
publicabstractvoidbuildCostume();
publicabstractvoidbuildHairstyle();
public Actor createActor()
{
return actor;
}
}
//具体建造者
classHeroBuilderextendsActorBuilder
{
publicvoidbuildType(){ actor.setType("英雄"); }
publicvoidbuildFace(){ actor.setFace("英俊"); }
publicvoidbuildCostume(){ actor.setCostume("盔甲"); }
publicvoidbuildHairstyle(){ actor.setHairstyle("飘逸"); }
}
classAngleBuilderextendsActorBuilder
{
publicvoidbuildType(){ actor.setType("天使"); }
publicvoidbuildFace(){ actor.setFace("漂亮"); }
publicvoidbuildCostume(){ actor.setCostume("白裙"); }
publicvoidbuildHairstyle(){ actor.setHairstyle("披肩长发"); }
}
classDevilBuilderextendsActorBuilder
{
publicvoidbuildType(){ actor.setType("恶魔"); }
publicvoidbuildFace(){ actor.setFace("帅气"); }
publicvoidbuildCostume(){ actor.setCostume("黑衣"); }
publicvoidbuildHairstyle(){ actor.setHairstyle("红色"); }
}
// 指挥者类
classActorController
{
public Actor construct(ActorBuilder builder)
{
builder.buildType();
builder.buildFace();
builder.buildHairstyle();
builder.buildCostume();
return builder.createActor();
}
}
测试类:
publicclassTest{publicstaticvoidmain(String[] args){
ActorBuilder builder = new AngleBuilder();
ActorController controller = new ActorController();
Actor actor = controller.construct(builder);
System.out.println(actor.getType());
System.out.println(actor.getCostume());
System.out.println(actor.getHairstyle());
System.out.println(actor.getFace());
}
}
3 优化
3.1 省略Director
其实Director
是可以省略的,直接与Builder
合并,在Builder
中提供类似Direcotr
中的construct()
方法,并定义为静态方法,如:
abstractclassActorBuilder{protectedstatic Actor actor = new Actor();
publicabstractvoidbuildType();
publicabstractvoidbuildFace();
publicabstractvoidbuildCostume();
publicabstractvoidbuildHairstyle();
publicstatic Actor build(ActorBuilder builder)
{
builder.buildType();
builder.buildFace();
builder.buildHairstyle();
builder.buildCostume();
return actor;
}
}
同时客户端代码修改如下:
Actor actor = ActorBuilder.build(new AngleBuilder());//Actor actor = ActorBuilder.build(new HeroBuilder());
//Actor actor = ActorBuilder.build(new DevilBuilder());
再简单一点的可以省略createActor
中的参数:
abstractclassActorBuilder{protected Actor actor = new Actor();
publicabstractvoidbuildType();
publicabstractvoidbuildFace();
publicabstractvoidbuildCostume();
publicabstractvoidbuildHairstyle();
public Actor build()
{
buildType();
buildFace();
buildHairstyle();
buildCostume();
return actor;
}
}
同时客户端简化如下:
Actor actor = new AngleBuilder().build();
这两种方式简化了系统结构的同时又不影响灵活性以及可扩展性,但是加重了抽象建造者的职责,如果build
方法较为复杂,待构建的产品组成部分较多,建议还是将其单独封装在Director
中,这样更加符合SRP(单一权责原则)。
3.2 钩子方法
钩子方法是一种可以控制是否调用某个buildXXX
的方法,特征如下:
- 返回类型为
boolean
- 方法名一般为
isXXX
例如修改ActorBuilder
如下:
abstractclassActorBuilder{protected Actor actor = new Actor();
publicabstractvoidbuildType();
publicabstractvoidbuildFace();
publicabstractvoidbuildCostume();
publicabstractvoidbuildHairstyle();
publicbooleanisBareheaded()
{
returnfalse;
}
public Actor createActor()
{
return actor;
}
}
并修改DevilBuilder
,覆盖默认方法:
classDevilBuilderextendsActorBuilder{publicvoidbuildType(){ actor.setType("恶魔"); }
publicvoidbuildFace(){ actor.setFace("帅气"); }
publicvoidbuildCostume(){ actor.setCostume("黑衣"); }
publicvoidbuildHairstyle(){ actor.setHairstyle("红色"); }
publicbooleanisBareheaded(){ returntrue; }
}
最后修改ActorController
:
classActorController{public Actor construct(ActorBuilder builder)
{
builder.buildType();
builder.buildFace();
builder.buildCostume();
if(builder.isBareheaded())
builder.buildHairstyle();
return builder.createActor();
}
}
相比起之前的ActorController
多了一次判断,测试如下:
publicstaticvoidmain(String[] args){ActorController controller = new ActorController();
Actor actor = controller.construct(new AngleBuilder());
System.out.println(actor.getType());
System.out.println(actor.getCostume());
System.out.println(actor.getHairstyle());
System.out.println(actor.getFace());
System.out.println();
actor = controller.construct(new DevilBuilder());
System.out.println(actor.getType());
System.out.println(actor.getCostume());
System.out.println(actor.getHairstyle());
System.out.println(actor.getFace());
}
输出如下:
3.3 返回Builder
在实际应用中Director
较少出现,通常只有Builder
以及Product
,而且Builder
是作为Product
的内部类,提供一系列set
方法,这些set
方法返回一个Builder
方便后续调用,最后以一个build()
结尾,比如OkHttp中的Request
/OkHttpClient
:
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(5000,TimeUnit.MILLISECONDS)
.readTimeout(10,TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("https://xxx")
.post(requestBody)
.build();
4 主要优点
- 封装细节:建造者模式中客户端不需要知道产品内部组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 扩展性好:每一个具体建造者都相对独立,而与其他建造者无关,伊尼茨可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,扩展方便,符合开闭原则
- 控制创建过程:将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,更加精细地控制创建过程
5 主要缺点
- 范围受限:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分不相同,就不适合使用建造者模式,因此使用范围收到一定限制
- 建造者多:如果产品内部结构复杂多变,可能会需要定义很多具体建造者类来实现这种变化,增大系统的理解难度与运行成本
6 适用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序
- 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入指挥者类,将创建过程封装在指挥者类中,而不再建造者类或者客户类中
- 隔离复杂对象的创建与使用,并使得相同的创建过程可以创建不同的产品
7 总结
以上是 设计模式学习笔记(八):建造者模式 的全部内容, 来源链接: utcz.com/a/33378.html