定义及适用场景
建造者(Builder)模式,是一种创建型的设计模式。使用建造者这样一个名称命名它,是因为这个设计模式专注于对象复杂的建造过程。而非像工厂模式那般仅注意对象的产生。
它适用于类似生成游戏人物这样的场景。一个游戏人物,需要很多的步骤来完成设计,像基础数据设定,技能选择,生涯选择等等。
值得注意的是,这些都是构建人物对象的生产过程,而非人物对象本身。建造者模式的一个特点,便是它使得模型同其生产过程分离,减少了类的耦合性,增加了架构的灵活性。
因为这样一来,如何生产对象,如何将对象从new出的一个胚子逐步的点缀修饰,成为一个最终的产品,就变成了建造者需要实现的内容,而与对象类本身无关,这保证了产品本身同生产流程的解耦。
另外,一个对象如何生产,仅受建造者类控制。因此,如果需要改变或者新加对象的生产流程,仅需要更改建造者即可,它不影响场景使用,也不影响对象模型。这保证了设计的灵活性。
类的组成
建造者模式的实现,有赖于四个部分的组成,他们分别是:
- product,产品类,是需要被创建的产品,包含多个需要被创建的部分。
- Builder,抽象建造者类,负责制定抽象的产品生产方法,定义装配过程。
- ConcreteBuilder,具体建造者类,它们是一种类,负责具体实现不同版本产品的各个装配具体流程。
- Director,指导者类,也叫导演类,负责按照一定顺序调用建造者的方法,指导对象的生产,将产品最终生产出来。一般来说,导演类会聚合一个建造者类,但不与产品类发生关联。
下面,我们以一个场景作为体现。
场景需求
塔防游戏存在多种怪物,他们形象各异,属性不同,需要构建的部分包含图像和属性。现需要设计结构,使得在场景中方便创建对应怪物,同时需要便于增改怪物的构建。
我们选用建造者模式来完成这个设计。首先先完成类图的设计。
- 产品类:Monster包含了基础属性和图像
- 抽象建造者类:组合了一个产品类用于生产,制定了生产产品所需的几个步骤:获得胚子,设定属性,设定图像,完工出炉。
- 具体建造者类:继承抽象建造者类,重写父类设定属性和设定图像的方法,以此建造不同类型的对象。
- 导演类:内部聚合了一个生产者,作为生产产品的蓝图这个成员应该允许改变,以生产不同的产品。提供生产方法,用以指导建造者构建对象并返回成品。
完成这四个类的设计后,我们还需要在场景类中调用这些个类,那么加入场景类后,类图将变成:
可以看到,对于场景类来说,实现对象的创建并不需要与产品类产生依赖,仅需要使用导演类并为其指定生产蓝图(具体建造者类即可)
最终实现
下面,我们就可以根据刚刚的设计,将想法付诸实践了。为了方便演示,我们设定两种怪物:哥布林和暗夜骑士,并暂时将图像改做英雄出场台词。
java版本
产品类
public class Monster {
private int HP;
private int speed;
private int damage;
private String name;
private String graph;
public String getGraph() {
return graph;
}
public void setGraph(String graph) {
this.graph = graph;
}
public int getHP() {
return HP;
}
public void setHP(int hP) {
HP = hP;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getDamage() {
return damage;
}
public void setDamage(int damage) {
this.damage = damage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "name:" + name + ",HP:" + HP + ",speed:" + speed + ",damage:" + damage + "\n";
}
}
抽象建造者类
abstract public class AbstractMonsterBuilder {
protected Monster monster;
abstract public void setAttribute();
abstract public void setGraph();
public Monster getMonster(){
return monster;
}
public void getNew() {
monster = new Monster();
}
}
具体建造者类
哥布林类
public class Monster1Builder extends AbstractMonsterBuilder {
@Override
public void setAttribute() {
monster.setDamage(10);
monster.setHP(100);
monster.setName("哥布林");
monster.setSpeed(50);
}
@Override
public void setGraph() {
monster.setGraph("哥布林冲锋!!!!!");
}
}
暗夜骑士类
public class Monster2Builder extends AbstractMonsterBuilder {
@Override
public void setAttribute() {
monster.setDamage(30);
monster.setHP(200);
monster.setName("暗夜骑士");
monster.setSpeed(150);
}
@Override
public void setGraph() {
monster.setGraph("感受黑暗的铁蹄吧!!!!");
}
}
导演类
public class MonsterBuildDirector {
private AbstractMonsterBuilder builder;
MonsterBuildDirector(AbstractMonsterBuilder builder){
setBuilder(builder);
}
public void setBuilder(AbstractMonsterBuilder builder) {
this.builder = builder;
}
public Monster Construct(){
//获得胚子
builder.getNew();
//设置属性
builder.setAttribute();
//设置图像(开场白)
builder.setGraph();
//返回产品
return builder.getMonster();
}
}
为了便于导演类更换生产图纸,在类中添加了setBuilder
方法以中途更换Builder
场景类
public class Client {
static public void main(String[] args) {
Monster monster = null;
//初始化一个导演类对象,默认基于哥布林的生产图纸
MonsterBuildDirector director = new MonsterBuildDirector(new Monster1Builder());
//生产一个哥布林,并输出属性和开场白
monster = director.Construct();
System.out.print(monster);
System.out.println(monster.getGraph());
System.out.println("----------------------------------");
//更改图纸为暗夜骑士
director.setBuilder(new Monster2Builder());
//生产一个暗夜骑士,并输出属性和开场白
monster = director.Construct();
System.out.print(monster);
System.out.println(monster.getGraph());
}
}
执行结果
C++版本
产品类
#ifndef MONSTER_H
#define MONSTER_H
class Monster
{
public:
Monster();
string Getname() { return name; }
void Setname(string val) { name = val; }
int Getdamage() { return damage; }
void Setdamage(int val) { damage = val; }
int Getspeed() { return speed; }
void Setspeed(int val) { speed = val; }
int GetHP() { return HP; }
void SetHP(int val) { HP = val; }
string Getgraph() { return graph; }
void Setgraph(string val) { graph = val; }
private:
string name;
int damage;
int speed;
int HP;
string graph;
};
#endif // MONSTER_H
抽象建造者类
#ifndef BUILDER_H
#define BUILDER_H
#include"Monster.h"
class Builder
{
public:
virtual ~Builder();
Monster* getMonster(){return monster;}
void getNew(){monster = new Monster();}
virtual void setAttribute() = 0;
virtual void setGraph() = 0;
protected:
Monster * monster;
};
#endif // BUILDER_H
具体建造者类
哥布林
#ifndef BUILDER_H
#define BUILDER_H
#include"Monster.h"
class Builder
{
public:
virtual ~Builder();
Monster* getMonster(){return monster;}
void getNew(){monster = new Monster();}
virtual void setAttribute() = 0;
virtual void setGraph() = 0;
protected:
Monster * monster;
};
#endif // BUILDER_H
#include "Monster1Builder.h"
void Monster1Builder::setAttribute()
{
monster->Setname("哥布林");
monster->Setdamage(10);
monster->Setspeed(50);
monster->SetHP(100);
}
void Monster1Builder::setGraph()
{
monster->Setgraph("哥布林冲锋!!!!");
}
暗夜骑士
#ifndef MONSTER2BUILDER_H
#define MONSTER2BUILDER_H
#include <Builder.h>
class Monster2Builder : public Builder
{
public:
virtual void setAttribute();
virtual void setGraph();
};
#endif // MONSTER2BUILDER_H
#include "Monster2Builder.h"
void Monster2Builder::setAttribute()
{
monster->Setname("暗夜骑士");
monster->Setdamage(30);
monster->Setspeed(150);
monster->SetHP(200);
}
void Monster2Builder::setGraph()
{
monster->Setgraph("感受黑暗的铁蹄吧!!!!");
}
导演类
#ifndef DIRECTOR_H
#define DIRECTOR_H
#include"Builder.h"
class Director
{
public:
Director(Builder* builder):builder(builder){}
~Director();
void Setbuilder(Builder* val) {
delete builder;
builder = val;
}
Monster* construct();
private:
Builder* builder;
};
#endif // DIRECTOR_H
#include "Director.h"
Director::~Director()
{
delete builder;
}
Monster * Director::construct()
{
builder->getNew();
builder->setAttribute();
builder->setGraph();
return builder->getMonster();
}
场景
void Monster1Builder::setAttribute()
{
monster->Setname("哥布林");
monster->Setdamage(10);
monster->Setspeed(50);
monster->SetHP(100);
}
void Monster1Builder::setGraph()
{
monster->Setgraph("哥布林冲锋!!!!");
}