在软件开发的过程中,当遇到一个“复杂的对象”,该对象由好多的部分组成,各个部分的组合比较稳定或有一定的依赖次序,但各部分自身却会经常面临着变化。如何隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”,这就是所谓的建造模式。
例子:
买肯德基 :(Terrylee的例子)典型的儿童餐包括一个主食,一个辅食,一杯饮料和一个玩具(例如汉堡、炸鸡、可乐和玩具车)。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的。
客户端:顾客。想去买一套套餐(这里面包括汉堡,可乐,薯条),可以有1号和2号两种套餐供顾客选择。
指导者角色: 收银员。知道顾客想要买什么样的套餐,并告诉餐馆员工去准备套餐。
建造者角色: 餐馆员工。按照收银员的要求去准备具体的套餐,分别放入汉堡,可乐,薯条等。
产品角色: 最后的套餐,所有的东西放在同一个盘子里面。
计算工资: 工资的计算一般是:底薪+奖金-税。但底薪分为一级8000、二级6000、三级4000三个等 级。根据岗位不同奖金的发放也不一样,管理及日常事务处理岗位(A类)每月根据领导及同事间的评议得分计算奖金,销售岗位(B类)则根据销售额发放提成。 税金则根据奖金和底薪的数额进行计算。由此看出该工资的计算方式是比较稳定的构建算法,但对工资的每一部分都会根据不同的情况产生不同的算法,如何将客户 端与变化巨烈的底薪、奖金和税金计算方式分离呢,这也比较适合用建造者模式。
Builder模式的架构
抽象建造者(Builder): 给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
具体建造者(Concrete Builder): 实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
指导者(Director): 调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
产品(Product): 要创建的复杂对象。
程序实现:
//指导者
class Director
{
public void build(Builder b )
{
b.BuildPartA();
b.BuildPartB();
}
}
//抽象建造者
abstract class Builder
{
//创建产品的第一部分
public abstract void BuildPartA();
//创建产品的第二部分
public abstract void BuildPartB();
}
//具体建造者类
class ConcreteBuilder1 : Builder
{
Product p = new Product();
public override void BuildPartA()
{
p.Add("Part1","Part1");
}
public override void BuildPartB()
{
p.Add("Part2","Part2");
}
//返回产品对象
public Product GetProduct()
{
return p;
}
}
//具体建造者类
class ConcreteBuilder2 : Builder
{
Product p = new Product();
public override void BuildPartA()
{
p.Add("PartA","PartA");
}
public override void BuildPartB()
{
p.Add("PartB", "PartB");
}
//返回产品对象
public Product GetProduct()
{
return p;
}
}
//产品类
class Product
{
StringDictionary sd = new StringDictionary();
public void Add(string name, string value)
{
sd.Add(name, value);
}
public void Show()
{
foreach (DictionaryEntry de in sd)
{
Console.WriteLine(de.Key+"\t"+de.Value+"\n");
}
}
}
//客户程序
class Client
{
public static void Main(string[] args)
{
Director d = new Director();
Builder b1 = new ConcreteBuilder1();
Builder b2 = new ConcreteBuilder2();
d.build(b1);
d.build(b2);
((ConcreteBuilder1)b1).GetProduct().Show();
((ConcreteBuilder2)b2).GetProduct().Show();
}
}
(车延禄)
程序举例:创建一个证件生成的程序。
任何证件生成都需要两个部分组成--姓名和号码。姓名由“姓”和“名”组成,而号码则根据不同证件由不同的生成方式。如:学生证("前缀"+号码),身份证("省份"+"城市"+"出生日期"+"随机数")。
产品类如下实现:
class Certificate
{
//证件名称
private string name;
//证件号码
private string cardid;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public string CardID
{
get
{
return cardid;
}
set
{
cardid = value;
}
}
//显示证件信息
public void show()
{
Console.WriteLine(name + "\n" + cardid);
}
}
抽象建造者:
interface IBuilder
{
//生成姓名
void BuildName();
//生成证件号码
void BuildCardID();
}
具体建造者(生成学生证):
class StudentBuilder : IBuilder
{
//证件对象
Certificate p = new Certificate();
private string firstname ; //名字
private string lastname ; //姓氏
private string cardprefix ; //学生证前缀
private string cardno ; //学生证号码
public StudentBuilder(string fname, string lname, string cprefix, string cno)
{
this.firstname = fname;
this.lastname = lname;
this.cardprefix = cprefix;
this.cardno = cno;
}
//建造学生证姓名
public void BuildName()
{
p.Name = lastname + firstname;
}
//建造学生证号码
public void BuildCardID()
{
p.CardID = cardprefix + cardno;
}
//返回证书对象
public Certificate GetCertificate()
{
return p;
}
}
具体建造者(生成身份证)
class PersonBuilder : IBuilder
{
//证书对象
Certificate p = new Certificate();
private string lastname ; //名字
private string firstname ; //姓氏
private string provno ; //省份代号
private string cityno ; //城市代号
private DateTime birthday ; //出生日期
private string randomcode ; //生成身份证号的后三位随机数
public PersonBuilder(string lname, string fname, string pno, string cno, DateTime birth, string randomcode)
{
this.lastname = lname;
this.firstname = fname;
this.provno = pno;
this.cityno = cno;
this.birthday = birth;
this.randomcode = randomcode;
}
//建造姓名对象
public void BuildName()
{
p.Name = lastname + firstname;
}
//建造身份证号码
public void BuildCardID()
{
string strbirth = birthday.Year.ToString() + birthday.Month.ToString() + birthday.Day.ToString();
p.CardID = provno + cityno + strbirth + randomcode;
}
//返回证件对象
public Certificate GetCertificate()
{
return p;
}
}
指导者对象:
class Director
{
public void Build(IBuilder b )
{
b.BuildName();
b.BuildCardID();
}
}
客户端对象:
class Client
{
public static void Main()
{
//创建指导者
Director d = new Director();
Console.WriteLine("请输入要创建的证书类型:(0-驾照,1-身份证)");
string type = Console.ReadLine();
switch (type)
{
case "0":
//实例化学生证具体生成器
IBuilder b1 = new StudentBuilder("伯通", "周", "鲁教字:", "99999999");
d.Build(b1);
b1.GetCertificate().show();
break;
case "1":
//实例化身份证具体生成器
IBuilder b2 = new PersonBuilder("峰", "欧阳", "3701", "01", new DateTime(1976, 11, 23), "89x");
d.Build(b2);
b2.GetCertificate().show();
break;
default:
break;
}
}
}
每一个具体的建造者是相互独立的,完成某一类产品的组装生成,可以完成产品在组装过程中实现更精细的控制。
在客户端程序中无需了解产品的构造,只需要实例化指导者对象和具体建造者对象,然后使用指导者对象来控制具体建造者的的建造过程和建造次序。
在指导者对象中用来控制具体建造对象的建造过程。
在具体建造者对象中用来实现产品每一部分的具体建造过程。
在产品对象用来描述产品的各部件的组成。
建造者模式用来控制产品的建造过程,实现产品对象的组成变化与客户程序的分离,而工厂模式实现产品类型的变化与客户端程序的分离。
建造模式的演化:
省略抽象建造者:
如果只有一个具体建造者的话,那可以省略抽象建造者。
class Director
{
ConcreteBuilder b = new ConcreteBuilder();
public void build()
{
b.BuildPartA();
b.BuildPartB();
}
}
如果只有一个具体建造者,除了可以省略抽象建造者,还可以把指导者也省略,让具体建造者也承担指导者的角色。
class ConcreteBuilder : Builder
{
Product p = new Product();
public override void BuildPartA()
{
p.Add("Part1","Part1");
}
public override void BuildPartB()
{
p.Add("Part2","Part2");
}
//返回产品对象
public Product GetProduct()
{
return p;
}
//具体建造者承担的指导者的角色功能
public void Construct()
{
BuildPartA();
BuildPartB();
}
}
一般来说在以下两种情况下应当考虑使用建造者模式:
1、被建造的对象有复杂的组成部分,每一部分的生成算法又不一样。(采用具体建造者不同的方法,实现对象不同部分的构造)
2、被建造的对象各部分有一定的组成顺序。(用Director对ConcreteBuild方法的调用次序来控制)