设计模式-建造者模式
1、建造小人
用程序画一个小人,简单一点,要求就是,小人要有头,身体,双手,双脚。
我们很简单的就可以实现这个程序:
Pen pen = new Pen(Color.Black); Graphics graphics = pictureBox1.CreateGraphics(); graphics.DrawEllipse(pen, 50, 20, 30, 30);//头 graphics.DrawRectangle(pen, 60, 50, 10, 50);//身体 graphics.DrawLine(pen, 60, 50, 40, 100);//左手 graphics.DrawLine(pen, 70, 50, 90, 100);//右手 graphics.DrawLine(pen, 60, 100, 45, 150);//左脚 graphics.DrawLine(pen, 70, 100, 85, 150);//右脚
运行结果如下:
如果要求画一个身体比较胖的小人呢?
这个也很简单:
Pen pen = new Pen(Color.Black); Graphics graphics = pictureBox1.CreateGraphics(); graphics.DrawEllipse(pen, 50, 20, 30, 30); graphics.DrawEllipse(pen, 45, 50, 40, 50); graphics.DrawLine(pen, 52, 50, 30, 100); graphics.DrawLine(pen, 80, 50, 100, 100); graphics.DrawLine(pen, 60, 100, 45, 150);
但是运行的结果如下:
原来是代码中少画了一条腿。加上下面这个代码就可以了
graphics.DrawLine(pen, 70, 100, 85, 150);
2、建造小人2
画人的时候,头身手脚都是必不可少的,不管是什么人物,开发的时候是不能少的。
目前,代码全写在Form1.cs的窗体里,要是需要在别的地方用这些画小人的程序怎么办?
自然而然,我们会想到分离,建两个类,一个是胖人的类,一个是瘦人的类。不管是谁都可以调用它了。
/// <summary> /// 瘦人的类 /// </summary> class PersonThinBuilder { private Graphics Graphics; private Pen Pen; /// <summary> /// 初始化的时候确定画板和颜色 /// </summary> /// <param name="graphics"></param> /// <param name="pen"></param> public PersonThinBuilder(Graphics graphics, Pen pen) { Graphics = graphics; Pen = pen; } /// <summary> /// 建造小人 /// </summary> public void Build() { Graphics.DrawEllipse(Pen, 50, 20, 30, 30);//头 Graphics.DrawRectangle(Pen, 60, 50, 10, 50);//身体 Graphics.DrawLine(Pen, 60, 50, 40, 100);//左手 Graphics.DrawLine(Pen, 70, 50, 90, 100);//右手 Graphics.DrawLine(Pen, 60, 100, 45, 150);//左脚 Graphics.DrawLine(Pen, 70, 100, 85, 150);//右脚 } }
胖人的类,也类似。然后,我在客户端,只需要这样写就可以了。
Pen pen = new Pen(Color.Black); Graphics graphics = pictureBox1.CreateGraphics(); PersonThinBuilder personThinBuilder = new PersonThinBuilder(graphics, pen); personThinBuilder.Build(); Graphics graphicsFat = pictureBox2.CreateGraphics(); PersonFatBuilder personFatBuilder = new PersonFatBuilder(graphicsFat, pen); personFatBuilder.Build();
运行结果如下:
这样写,确实达到了可以复用这两个画小人的程序,但是缺胳膊少腿的可能依然存在,比如,现在,需要画一个比较高的小人呢?
所以,最好的办法就是规定,凡是建造小人,都必须要有头、身体、双手、双脚。
3、建造者模式
仔细分析会发现,这里建造小人的“过程”是稳定的,都需要头身手脚,而具体建造的“细节”是不同的,有胖瘦高矮。但是对于用户来讲,我才不管你这些呢,我只想告诉你,我需要一个胖小人来游戏,你给我建造一个就行了。
如果你需要将一个复杂对象的构建与它的表示分离,使得同样的构建过程,可以创建不同的表示的意图时,我们需要应用于一个设计模式,建造者(Builder)模式,又叫生成器模式。建造者模式可以将一个产品的内部表现与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表现的产品对象。如果我们用了建造者模式,那么用户就只需要制定需要建造的类型就可以得到他们,而具体建造的过程和细节就不需要知道了。
建造者模式(Builder),将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
那怎么使用建造者模式呢?
一步一步来,首先我们要画小人,都需要画什么呢?头、身体、左手、右手、左脚、右脚。
所以我们先定义一个抽象的建造人的类,来把这个过程给稳定住,不让任何人遗忘当中的任何一步。
abstract class PersonBuilder { protected Graphics Graphics; protected Pen Pen; public PersonBuilder(Graphics graphics, Pen pen) { Graphics = graphics; Pen = pen; } public abstract void BuildHead(); public abstract void BuildBody(); public abstract void BuildArmLeft(); public abstract void BuildArmRight(); public abstract void BuildLegLeft(); public abstract void BuildLegRight(); }
然后,我们需要建造一个瘦的小人,则让这个瘦子类去继承这个抽象类,那就必须去重写这些抽象方法了,否则编译器也不让你通过。
class PersonThinBuilder : PersonBuilder { public PersonThinBuilder(Graphics graphics, Pen pen) : base(graphics, pen) { } public override void BuildArmLeft() { Graphics.DrawLine(Pen, 60, 50, 40, 100); } public override void BuildArmRight() { Graphics.DrawLine(Pen, 70, 50, 90, 100); } public override void BuildBody() { Graphics.DrawRectangle(Pen, 60, 50, 10, 50); } public override void BuildHead() { Graphics.DrawEllipse(Pen, 50, 20, 30, 30); } public override void BuildLegLeft() { Graphics.DrawLine(Pen, 60, 100, 45, 150); } public override void BuildLegRight() { Graphics.DrawLine(Pen, 70, 100, 85, 150); } }
当然,胖子或者高个子其实都用类似的代码去实现这个类就可以了。
但是,在客户端调用是,还是需要知道头身手脚这些方法,还是没有解决。
我们还缺在建造者模式中一个很重要的类,指挥者(Director),用它来控制建造过程,也用它来隔离用户与建造过程的关联。
class PersonDirector { private PersonBuilder PersonBuilder; public PersonDirector(PersonBuilder personBuilder) { PersonBuilder = personBuilder; } public void CreatePerson() { PersonBuilder.BuildHead(); PersonBuilder.BuildBody(); PersonBuilder.BuildArmLeft(); PersonBuilder.BuildArmRight(); PersonBuilder.BuildLegLeft(); PersonBuilder.BuildLegRight(); } }
PersonDirector类的目的就是根据用户的选择来一步一步的建造小人,而建造的过程在指挥者这里完成,用户就不需要知道了,而且,由于这个过程每一步都是一定要做的,那就不会让少了一只手,少画了一条腿的问题出现了。
代码结构图如下:
那么,在客户端的代码,应该也不难实现了。
Pen pen = new Pen(Color.Black); PersonThinBuilder personThinBuilder = new PersonThinBuilder(pictureBox1.CreateGraphics(), pen); PersonDirector personDirectorThin = new PersonDirector(personThinBuilder); personDirectorThin.CreatePerson();
试想一下,如果需要增加一个高个子和矮个子的小人,应该怎么做呢?
加两个类,一个高个子类,和一个矮个子类,让它们都去继承PersonBuilder,然后在客户端调用就可以了。
但是,如果需要细化一些,比如人的五官,手的上臂、前臂和手掌,大腿小腿这些,该怎么办呢?
这就需要权衡,如果这些细节是每个具体小人都需要构建的,那就应该要加进去,反之,就没有必要。其实建造者模式是逐步建造产品的,所以建造者的Builder类里面的那些建造方法必须要走狗普遍,以便为各种类型的具体建造者构造。
4、建造者模式解析
建造者模式(Builder)结构图:
现在来看这张图就不会感觉陌生了,总结一下,Builder是什么?
概括的说,是为创建一个Product对象的各个部件指定的抽象接口。
ConcreteBuilder是什么呢?
是具体的建造者,实现Builder接口,构造和装配各个部件。Product当然就是那些具体的小人,产品角色了。
Director是什么?
指挥者,是构建一个使用Builder接口的对象。
那都是什么时候需要使用建造者模式呢?
主要是用于创建一些复杂的对象,这些对象的内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临着负责的变化。
我们试着把建造者模式的基本代码推演一下,一遍有一个更宏观的认识。
5、建造者模式基本代码
Product类——产品类,由多个部件组成。
class Product { IList<string> parts = new List<string>(); public void Add(string part) { parts.Add(part); } public void Show() { Console.WriteLine("\n产品 创建---"); foreach (string part in parts) { Console.WriteLine(part); } } }
Builder类——抽象建造者类,确定产品由两个部件PartA和PartB组成,并生命一个得到产品建造后结果的GetResult。
abstract class Builder { public abstract void BuildPartA(); public abstract void BuildPartB(); public abstract Product GetResult(); }
ConcreteBuilder1类——具体建造者类。
class ConcreteBuilder1 : Builder { private Product product = new Product(); public override void BuildPartA() { product.Add("部件A"); } public override void BuildPartB() { product.Add("部件B"); } public override Product GetResult() { return product; } }
ConcreteBuilder2类——具体建造者类。
class ConcreteBuilder2 : Builder { private Product product = new Product(); public override void BuildPartA() { product.Add("部件X"); } public override void BuildPartB() { product.Add("部件Y"); } public override Product GetResult() { return product; } }
Director类——指挥者类。
class Director { /// <summary> /// 用来指挥建造过程 /// </summary> /// <param name="builder"></param> public void Construct(Builder builder) { builder.BuildPartA(); builder.BuildPartB(); } }
客户端代码,客户不需要知道具体的构建过程。
static void Main(string[] args) { Director director = new Director(); Builder builder1 = new ConcreteBuilder1(); Builder builder2 = new ConcreteBuilder2(); director.Construct(builder1); //指挥者用ConcreteBuilder1的方法来建造产品 Product product1 = builder1.GetResult(); product1.Show(); director.Construct(builder2); //指挥者用ConcreteBuilder2的方法来建造产品 Product product2 = builder2.GetResult(); product2.Show(); Console.ReadLine(); }
运行结果如下:
所以说,建造者模式应该是在当创建复杂对象的算法应该独立于改对象的组成部分以及他们的装配方式时适用的模式。