在GOF所著的《设计模式》一书中,描述了Builder模式的意图:“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。”按照封装变化的原理,Builder模式实则是封装对象创建的变化,但它与Factory Method模式、Abstract Factory模式不同的是,所谓对象的创建,主要是指对象内部构件的创建。形象地说,Builder模式就好似生产线的装配工人,可以接收多种方式与顺序组装各种零部件。本章,将给出我参与设计与开发的CIMS系统中的一个需求,详细讲解Builder模式的应用。
1 需求分析——装配设备对象
在CIMS(Computer Integrated Manufacture System)项目中,有这样一个需求。系统中需要创建Equipment对象,这些对象由Machine对象和多个Port对象组成。Port对象包含两种类型:Input与Output。在Machine对象中,定义了PortType属性,它的值与Port对象的类型相对应。从目前的需求来看,Equipment对象,存在如下三种组成情况。
— 一个Machine对象,一个Input类型的Port对象;
— 一个Machine对象,一个Output类型的Port对象;
— 一个Machine对象,一个Input类型的Port对象,一个Output类型的Port对象。
客户方不排除将来有增加新的Equipment组合的可能。
需求清晰而简单,我们可以非常容易地识别出Port、Machine、Equipment对象。Port类的定义如下:
public abstract class Port ...{ public abstract void Transfer(); } public class InputPort:Port ...{ public override void Transfer() ...{ Console.WriteLine("Input"); } } public class OutputPort:Port ...{ public override void Transfer() ...{ Console.WriteLine("Output"); } }
Port对象由于类型的不同,方法Transfer会有不同的实现,因此,我们定义了一个抽象Port类,然后定义其子类InputPort和OutputPort,分别代表两种不同类型的Port。
Machine类的定义相对比较简单,如下所示。
public class Machine ...{ public Machine() ...{ } public Machine(string name) ...{ m_name = name; } private string m_name; private string m_portType; public string Name ...{ get ...{ return m_name; } set ...{ m_name = value; } } public string PortType ...{ get ...{ return m_portType; } set ...{ m_portType = value; } } public void Run() ...{ Console.WriteLine("The machine {0} is running!", m_name); }
由于Equipment对象可能会包含多个Port,因此Equipment类引入集合对象来保存Port对象,并利用泛型限制集合元素的类型,通过强类型的方式避免强制转换时可能出现的异常或错误。
public class Equipment ...{ public Equipment() ...{ m_list = new List<Port>(); } private Machine m_machine; private List<Port> m_list; private string m_name; public Machine Machine ...{ get ...{ return m_machine; } set ...{ m_machine = value; } } public List<Port> PortsList ...{ get ...{ return m_list; } } public string Name ...{ get ...{ return m_name; } set ...{ m_name = value; } } public void AddPort(Port port) ...{ m_list.Add(port); } public void RemovePort(Port port) ...{ m_list.Remove(port); } public void Run() ...{ Console.WriteLine("The Equipment {0} is running as below...",m_name); foreach (Port port in m_list) ...{ port.Transfer(); } m_machine.Run(); } }
Equipment的Run方法会遍历m_list中的元素,并执行Port类型的Transfer方法,然后再执行Machine对象的Run方法。
2 糟糕的设计
单以现有的需求而论,上面的设计已经基本能够满足客户的要求。例如,我们创建一个Equipment对象,它包含一个Machine对象,以及两个Port对象,一个为Input类型,一个为Output类型。
Machine machine = new Machine("InputOutputMachine"); Port inputPort = new InputPort(); Port outputPort = new OutputPort(); Equipment eqp = new Equipment(); eqp.Name = machine.Name; eqp.PortType = "InputOutput"; eqp.AddPort(inputPort); eqp.AddPort(outputPort);
然而,这样的实现存在以下三个明显的缺陷:
— 创建过程过于繁杂,不便于调用方调用;
— Port对象的添加过程无法控制;
— 过于僵化,不利于程序的扩展。
事实上,每当我们创建一个Equipment对象时,都需要执行与上面实现相似的代码。既然如此,我们可以将这样的创建职责进行封装,以便于程序的重用。我们首先想到的是工厂模式。既然是创建三种Equipment对象,利用简单工厂模式即可,例如,定义一个类SimpleLCDFactory。
public static class SimpleLCDFactory ...{ public static Equipment CreateInputEQP(string eqpName) ...{ Machine machine = new Machine("InputMachine"); Port inputPort = new InputPort(); Equipment eqp = new Equipment(); eqp.Name = machine.Name; eqp.PortType = "Input"; eqp.AddPort(inputPort); } public static Equipment CreateOutputEQP(string eqpName) ...{ Machine machine = new Machine("OutputMachine"); Port outputPort = new OutputPort(); Equipment eqp = new Equipment(); eqp.Name = machine.Name; eqp.PortType = "Output"; eqp.AddPort(outputPort); } public static Equipment CreateIOPutEQP(string eqpName) ...{ Machine machine = new Machine("InputOutputMachine"); Port inputPort = new InputPort(); Port outputPort = new OutputPort(); Equipment eqp = new Equipment(); eqp.Name = machine.Name; eqp.PortType = "InputOutput"; eqp.AddPort(inputPort); eqp.AddPort(outputPort); } }
这样一来,要创建一个Equipment对象就相对容易多了。
Equipment eqp = SimpleLCDFactory.CreateIOPutEQP("InputOutputMachine");
由于提供了专门的方法来创建Equipment对象,之前提到的前两个缺陷就被有效地克服了。然而,由于采用的简单工厂模式并没有将可能存在的创建变化进行抽象,导致这样的结构仍然僵化,不易于扩展。例如,在增加一个新的Equipment对象组合时,就需要在SimpleLCDFactory类中添加一个新的方法创建这个对象。
要支持未来需求可能的变化,也可以引入Factory Method模式,将创建Equipment对象的方法进行抽象,具体的实现留待各自的实现子类来完成。坦白说,这未尝不是一个好的实现方案。然而,考虑到这里要创建的Equipment对象,是由Port对象和Machine对象组成的,我们关注的创建行为,归根结底,是如何将Port对象和Machine对象创建好,并使之组合成我们需要的整体对象Equipment,因此,采用Builder模式也许是更好的选择。
注意:实际上,Factory Method模式与Builder模式并没有泾渭分明的区别,尤其针对Builder模式,可以将其转换为Factory Method模式,反之则不然。也就是说,Factory Method模式的应用更加广泛。如果一定要做出区分,那么可以说,二者同时生成产品,Factory Method模式主要用于生成一类完整的产品,而Builder模式则更关注于产品内部各种零件的组合,并最终组装为整体的产品。由于Builder模式对产品内部的创建进行了细分,因此对于那些内部具有一定结构的目标对象,如本例的Equipment,非常好的选择仍然是Builder模式。
3 引入Builder模式
就Builder模式来说,最重要的当然是Build行为的抽象。以本例而言,要创建一个Equipment对象,需要Build的是Port和Machine对象,所以我们可以定义两个Build方法的抽象。
void BuildPort(); void BuildMachine(string machineName);
按照这样一个原则,我们可以建立如图1所示的类图。
实现代码如下:
public abstract class EQPBuilder ...{ protected Equipment m_equipment; protected Machine m_machine; public EQPBuilder() ...{ m_equipment = new Equipment(); } public abstract void BuildPort(); public virtual void BuildMachine(string name) ...{ m_machine = new Machine(name); m_equipment.Name = name; m_equipment.Machine = m_machine; } public Equipment GetEQP() ...{ return m_equipment; } } public class InputEQPBuilder:EQPBuilder ...{ public override void BuildPort() ...{ Port port = new InputPort(); m_equipment.AddPort(port); } public override void BuildMachine(string name) ...{ base.BuildMachine(name); m_machine.PortType = "Input"; } } public class OutputEQPBuilder:EQPBuilder ...{ public override void BuildPort() ...{ Port port = new OutputPort(); m_equipment.AddPort(port); } public override void BuildMachine(string name) ...{ base.BuildMachine(name); m_machine.PortType = "Output"; } } public class IOPutEQPBuilder:EQPBuilder ...{ public override void BuildPort() ...{ Port inputPort = new InputPort(); Port outputPort = new OutputPort(); m_equipment.AddPort(inputPort); m_equipment.AddPort(outputPort); } public override void BuildMachine(string name) ...{ base.BuildMachine(name); m_machine.PortType = "InputOutput"; } }
由于Builder子类对于Port对象的创建完全不同,因此我们将父类EQPBuilder中的CreatePort方法定义为抽象方法;至于创建Machine对象的CreateMachine方法,则因为具有一些共同的逻辑,可以在父类中提供实现,所以被定义为虚方法,并将相同的逻辑抽象到父类中。EQPBuilder的子类需要重写父类的CreateMachine方法,并在调用父类的CreateMachine方法后,根据创建的Port对象的不同,分别设置Machine对象的PortType值。
此外,在抽象类EQPBuilder中,定义了GetEQP方法,用于返回一个Equipment对象,这个对象其实就是EQPBuilder类型对象所创建的产品。由于各个Builder子类返回Equipment对象的实现逻辑完全一样,因而被定义为普通方法。
在Builder模式的实现中,已经有了Product(产品)角色Equipment类对象,有了Builder(建造者)角色EQPBuilder类对象,以及它的派生子类。现在还缺少一个Director(指导者)角色,用以引入具体建造者角色,指导完成产品的创建。该角色类似于工厂模式中的工厂对象。因此,我将其定义为LCDFactory,便于调用者理解其职能。
public static class LCDFactory ...{ public static Equipment CreateEQP(EQPBuilder buider, string name) ...{ buider.BuildPort(); buider.BuildMachine(name); return buider.GetEQP(); } }
由于LCDFactory类的静态方法CreateEQP接受的参数是抽象类EQPBuilder,所以指导者角色与建造者角色之间仅存在弱依赖关系,保证了Builder的扩展不会影响产品的创建,类图如图2所示。
与标准的Builder模式不同,为了调用方便,我将EQPBuilder类型中的GetEQP方法也封装到了LCDFactory类中,因此客户端的调用方式应该如下所示。
class Program ...{ static void Main(string[] args) ...{ EQPBuilder builder = new InputEQPBuilder(); Equipment eqp = LCDFactory.CreateEQP(builder, "InputMachine"); eqp.Run(); builder = new IOPutEQPBuilder(); eqp = LCDFactory.CreateEQP(builder, "InputOutputMachine"); eqp.Run(); Console.Read(); } }
4 从容应对扩展
即使创建Equipment的需求发生了变化,应用了Builder模式的设计也能够从容应对。例如,要求创建的Equipment包含一个Machine对象,一个Input类型的Port,两个Output类型的Port,那么我们可以在不修改原有程序集的前提下,新定义一个IO2PutEQPBuilder类,并继承自抽象类EQPBuilder。
public class IO2PutEQPBuilder:EQPBuilder ...{ public override void BuildPort() ...{ Port inputPort = new InputPort(); Port outputPort1 = new OutputPort(); Port outputPort2 = new OutputPort(); m_equipment.AddPort(inputPort); m_equipment.AddPort(outputPort1); m_equipment.AddPort(outputPort2); } public override void BuildMachine(string name) ...{ base.BuildMachine(name); m_machine.PortType = "InputOutput2"; } }
此时,客户端可以根据传递进来的EQPBuilder类型对象,生产出新的Equipment产品。例如:
class Program ...{ static void Main(string[] args) ...{ EQPBuilder builder = new IO2PutEQPBuilder(); Equipment eqp = LCDFactory.CreateEQP(builder, "InputOutput2Machine"); eqp.Run(); Console.Read(); } }
我们甚至可以引入配置文件,利用反射技术动态创建具体的EQPBuilder对象,从而完全达到松散耦合、开放扩展的目的。本例是项目开发的真实实践,通过对Builder模式的引入,给程序结构带来了有利的变化。这足以证明,如果能够合理地运用设计模式,就足以弥补好的设计与坏的设计之间巨大的鸿沟。
作者简介:
|
张逸,网名Bruce Zhang、Wayfarer |
张逸作为一名高级软件工程师,曾先后在中兴通讯、HP任职,参与了AAA(Authorization Authentication Accounting)、BOE-CIMS(BOE-Computer Integration Manufacture System)、NCIC-CRM(Nissan-Customer Relation Management)等项目与模块的设计与开发。作为Microsoft MVP(Most Valuable Professional),他主要从事.NET平台下架构设计与开发的工作,熟悉C#,ASP.NET,Web Service,.NET Remoting,WCF等技术。张逸在面向对象领域具有一定的造诣,特别是设计模式、测试驱动开发、极限编程与UML等技术与思想的运用。著有《软件设计精要与模式》一书,由电子工业出版社出版。您可以通过电子邮件地址zhangyi_2003@163.com或者访问他的个人主页http://www.brucezhang.com与他交流。 |