生产管理知识软件的
可维护性与可复用性
模式:
模式,即 Pattern。其实就是解决某一类问题的方法论。把解决某类问题的方法总结归纳到理论
高度,那就是模式。Alexander 给出的经典定义是:每个模式都描述了一个在我们的环境中不断
出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已
有的解决方案,无需再重复相同的工作。模式有不同的领域,建筑领域有建筑模式,软件设计领
域也有设计模式。当一个领域逐渐成熟的时候,自然会出现很多模式。
设计模式和面向对象的设计模式:
设计模式(Designpattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经
验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计
模式最初来源于建筑学。GOF(“四人帮”,指 Gamma,Helm,Johnson&Vlissides,Addison-Wesley
四人)的《设计模式》(1995年出版)是第一次将设计模式提升到理论高度,并将之规范化,本
系列文章主要就是讲解这 23种经典的设计模式。
面向对象设计的模式,顾名思义,就是在面向对象分析与设计中使用的设计模式,GOF23 种设计
模式同时也是面向对象的设计模式,本文不做区分。良好的设计模式运用可以实现软件设计的“高
内聚、低耦合”,提高软件的复用性和可扩展性。
框架:
框架,即 Framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。
简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。框
架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层。
架构:
架构(Architecture)是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。架构
是一个系统的草图。架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和
相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某
个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。
一些刚入门的程序员经常会混淆“框架”和“架构”这两个名词,这里做了一下解释。
我们为什么要使用设计模式呢?有人可能会说为了设计出"高内聚低耦合"的软件。"高内聚低耦
合"的软件实际上也就是本文所说的具有可维护性和可复用性的软件。
这篇文章主要讲解两方面内容,这两方面是软件设计中很重要,也是很关键的内容,希望大家认
真思考并深刻理解。第一部分就是关于软件的可维护性和可复用性的相关内容,第二部分就是在
第一部分的基础上逐条讲解面向对象软件设计的基本原则,本文内容都是些很理论性的东西,这
些理论是软件设计的基础。凡是有理论的地方,就有如何恰当的将理论应用到实践中去的问题,
设计模式是对于学习 OO 设计原则的具体指导,也就是说设计模式就是将这些理论应用到实践的
一种成熟的方式。
软件的可维护性和可复用性
首先来上一段大师所说的话,很经典。
“通常认为,一个易于维护的系统,就是复用率较高的系统;而一个复用率较高的系统,就是一
个易于维护的系统。但是实际上,可维护性和可复用性是两个独立的目标,就像两只奔跑的兔子
一样,并不总是方向一致的。对于面向对象的软件系统设计来说,在支持可维护性
(Maintainability)的同时,提高系统的可复用性(Reuseability)是一个核心的问题。”--
《Java与模式》阎宏博士
软件系统的可维护性:
软件维护就是软件的再生。一个好的软件设计,必须能够允许新的设计要求以比较容易和平稳的
方式加入到已有的系统中去,从而使这个系统能够不断的的焕发出活力。一个可维护性较好的系
统,应当允许维护工作能够以容易、准确、安全和经济的形式进行。
【导致可维护性较低的原因】
1、过于僵硬:在系统中加入一个新的功能,不管大小都很难,不仅意味着建造一个独立的新的
模块,而且因为这个新功能会波及很多其他模块,最后成跨越几个模块的改动。
2、过于脆弱:与软件的过于僵硬同时存在,是软件系统在修改已有代码时过于脆弱。对一个地
方的修改,往往会导致看上去没有什么关系的另外一个地方发生故障。
3、复用率低:所谓复用,就是指一个软件的组成部分,可以在同一个项目的不同地方甚至另一
个项目中重复使用。复用率低,指当一段代码,函数,模块的功能可以在新的模块或新的系统使
用,但是已有代码依赖于其他很多东西,很难分开。
4、黏度过高:一个改动可以保存原始设计意图和原始设计框架的方式进行,也可以以破坏原始
意图和框架进行。第一种方法对系统的未来有利,第二种办法是权宜之计,可以解决短期的问题,
但是会牺牲中长期的利益。如果一个系统中使用第二种方法比使用第一种方法容易,那么就是黏
度过高。
【设计的目标】
1、可扩展性:新的性能可以很容易地加入到系统中去,就是可扩展性。这就是系统“过于僵硬”
的属性的方面。
2、灵活性:可以允许代码修改平稳地发生,而不会波及到很多其他的模块,这就是灵活性。灵
活性其实就是“过于脆弱”的属性的方面。
3、可插入性:可以很容易地将一个类抽出去,同时将另外一个有同样接口的类加入进来,这就
是可插入性。其实,这就是“黏度过高”的方面。
软件系统的可复用性:
【软件复用的好处】
1、较高的生产效率;
2、较高的软件质量;
3、恰当使用复用可以改善系统的可维护性。
【传统的复用形式】
1、代码的剪贴复用;
2、算法的复用;
3、数据结构的复用。
【面向对象设计的复用】
在面向对象语言中,数据的抽象化,继承,封装和多态性使得一个系统可以在更高层次上提供可
复用性。数据的抽象化和继承关系使得概念和定义可以复用;多态性使得实现和应用得到复用;
而抽象化和封装可以保持和促进系统的可维护性,复用的重点转移到含有宏观商业逻辑的抽象层
次上。在面向对象的设计里面,可维护性复用是以设计原则和设计模式为基础的。
提高系统可维护性和可复用性的设计原则
1、“开-闭”原则(Open-ClosedPrinciple,或者 OCP);
一个软件实体应该对扩展开放,对修改关闭;
在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。换言之,应当可以在
不必修改源代码的情况下改变这个模块的行为。这个原则实际上是对“对可变性的封闭原则“:
找到一个系统的可变因素,将之封装起来。这个原则意昧着两点:
1)一个可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的
不同表象意昧着同一个继承等级结构中的具体子类。
继承就当被看作是封装变化的方法,而不应当被认为是从一般的对象生成特殊对象的方法。
2)一种可变性不应当与另一种可变性混合在一起。(所有类图的继承结构一般不会超过两层,不
然就意昧着将两种不同的可变性混合在了一起。)
这个原则是总的原则,其它几条是这个原则的手段和工具。
2、里氏替代原则(LiskovSubstitutionPrinciple,或者 LSP);
如果对于每一个类型为 T1的对象 o1,都有类型为 T2的对象 o2,使得以 T1定义的所有程序 P在
所有的对象 o1都代换成 o2时,程序 P的行为没有变化,那么类型 T2是类型 T1的子类型。
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察
觉出基类对象和子类对象的区别。
反过来代换不成立。
3、依赖倒转原则(DependencyInversionPrinciple,或者 DIP);
要依赖于抽象,不要依赖于具体。
开闭原则是目标,而达到这一目标的手段是依赖倒转原则。
抽象层次包含的是应用系统的商务逻辑和宏观的、对整个系统来说重要的战略性决定,是必然性
的体现,那么抽象层次就应当是较为稳定的,应当是复用的重点;也应当是维护的重点;而具体
层次则含有一些次要的与实现有关的算法和逻辑,以及战术性的决定,带有相当大的偶然性选择。
具体层次的代码是会经常有变动的,不能避免出现错误。
4、接口隔离原则(InterfaceSegregationPrinciple,或者 ISP);
使用多个专门的接口比使用单一的总接口要好。
换言之,从一个客户类的角度讲:一个类对另一个类的依赖性应当是建立在最小的接口上的。
接口隔离原则与迪米特法则(下面讲到)都是对一个软件实体与其他的软件实体的通信限制。迪
米特原则要求尽可能地限制通信的宽度和深度,接品隔离原则要求通信的宽度尽可能地窄。这样
做的结果使一个软件系统在功能扩展过程当中,不会将修改的压力传递到其他对象。
一个接口相当于剧本中的一种角色,而此角色在一个舞台上由哪一个演员来演则相当于接口的实
现。因此,一个接口应当简单地代表一个角色,而不是多个角色。如果系统涉及到多个角色的话,
那么每一个角色都应当由一个特定的接口代表。
5、组合/聚合复用原则(Composition/AggregationPrinciple,或者 CARP);
组合/聚合原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的
对象通过向这些对象的委派达到得复用已有功能的目的。
要尽量使用组合/聚合,尽量不要使用继承。
6、迪米特法则(LawofDemeter,或者 LoD);
一个软件实体应当尽可能少的与其他实体发生相互作用。模块之间的交互要少。这样做的结果是
当系统的功能需要扩展时,会相对更容易地做到对修改的关闭。
一个对象应当对其他对象有尽可能少的了解。
7、单一职责原则(SingleResponsibilityPrinciple,或者 SRP)
在设计中为每种职责设计一个类,彼此保持正交,互不干涉。这个原则比较容易理解,这里不在
多说。
小结
当我们掌握了 C#的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用各种
框架与技术构建桌面以及 Web应用时,这并不意味着我们可以写出面向对象的程序,不意味着我
们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。
使用面向对象语言开发的程序不一定是面向对象的,使用面向过程的语言开发的程序也不一定不
是面向对象的。要想开发出一个具有可维护性和可复用性的软件系统,那是需要优秀的设计和长
时间的运行才能完成的,其实我们可以观察一下,任何一个优秀的软件产品都是经过长时间的设
计,运行,维护,修改等最后才成为成功的产品,版本上也在不断的更新。衡量一个软件开发者
是不是一个好的软件开发者,不是看他是否实现了软件的必要功能,而是要看你的软件在满足功
能需求的情况下是否做到了复用性和可扩展性,这对于一个大型系统尤其重要。我们不要静止的
看待一个软件,而一定要把软件过程放在时间轴上来观察与设计它,只有放在时间轴上经得住考
验的软件系统才是成功的。软件的复用性和可扩展性对于大型系统是必要的,我们在设计自己的
软件系统时,甚至在编写代码时更需要考虑一下这样做是否遵循了系统设计的原则,是否有利于
系统的可维护性和可复用性,是否达到了常说的“高内聚低耦合”呢?设计模式正是解决这一问
题的王道。
从下文开始我们将结合实例对于 GoF23种设计模式进行一一讲解。
现在我们正式进入 GoF23种设计模式中的创建型模式的讲解中来,创建型模式主要解决对象如何
创建的问题,提倡创建对象的责任和使用对象的责任分离,以达到更好对创建对象的控制的目的,
创建型模式主要包括抽象工厂(AbstractFactory),建造者(Builder),工厂方法(FactoryMethod),
原型(Prototype),单子(Singleton)。这篇文章主要分为两大部分内容,在第一部分中我将介绍
抽象工厂模式的原型,包括抽象工厂的意图,可以解决的问题,原型代码和 UML等,再结合一个
生活中的小例子进行原型的说明。第二部分我会结合实际项目来讲述一下抽象工厂模式是如何应
用的。最后我会对抽象工厂模式进行一个小结。
工厂模式的几种形态
工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不
必事先知道每次要实例化哪一个类。工厂模式有以下几种形态:
简单工厂(SimpleFactory)模式:又称静态工厂方法模式(StaticFactoryMethodPattern)。
主要是工厂中提供一个静态的方法用来根据不同的参数创建不同的抽象产品的具体实例,一般在
IoC 中应用比较多,例如通过反射机制和简单工厂模式可以解决依赖注入的问题。简单工厂模式
不属于 GoF23种设计模式,这里也就不再作过多的分析。感兴趣的园友可以找找相关资料。
工厂方法(FactoryMethod)模式:又称多态性工厂(PolymorphicFactory)模式或虚拟构造子
(VirtualConstructor)模式。这个模式属于 GoF23种设计模式之一,在后面的文章中会做详细
的介绍。
抽象工厂(AbstractFactory)模式:又称工具箱(Kit或 Toolkit)模式。这是本文的重点。
抽象工厂模式的原型
描述:
假设一个子系统需要一些产品对象,而这些产品对象又属于一个以上的产品等级结构。那么为了
将消费这些产品的责任和创建这些产品对象的责任分割开来,可以引进抽象工厂模式。这样的话,
消费产品的一方不需要直接参与产品的创建工作,而只需要向一个公用的工厂接口请求所需要的
产品。
意图:
抽象工厂模式可以向客户端(Client指代码模式的使用者,后文类同)提供一个接口,使得客户
端在不必指定产品的具体类型的情况下,创建多个产品族(ProductFamily 指位于不同产品等级
中,功能相关联的产品的集合)中的产品对象。
模式原型 UML:
抽象工厂涉及到以下角色:
抽象工厂(AbstractFactory)角色:声明一个操作集合的接口以创建抽象产品族。
具体工厂(ConcreteFactory)角色:实现创建具体产品族的抽象工厂的实现。
抽象产品(AbstractProduct)角色:声明一个产品的接口。
具体产品(Product)角色:定义了一个被具体工厂创建的产品对象,实现了抽象工厂接口。
客户端(Client)角色:使用抽象工厂和抽象产品的类。
模式原型代码:
usingSystem;
namespace
{
//测试程序
classMainApp
{
publicstaticvoidMain()
{
//抽象工厂 1
AbstractFactoryfactory1=newConcreteFactory1();
Clientc1=newClient(factory1);
();
//抽象工厂 2
AbstractFactoryfactory2=newConcreteFactory2();
Clientc2=newClient(factory2);
();
//等候用户输入
();
}
}
//抽象工厂
abstractclassAbstractFactory
{
publicabstractAbstractProductACreateProductA();
publicabstractAbstractProductBCreateProductB();
}
//具体工厂 1
classConcreteFactory1:AbstractFactory
{
publicoverrideAbstractProductACreateProductA()
{
returnnewProductA1();
}
publicoverrideAbstractProductBCreateProductB()
{
returnnewProductB1();
}
}
//具体工厂 2
classConcreteFactory2:AbstractFactory
{
publicoverrideAbstractProductACreateProductA()
{
returnnewProductA2();
}
publicoverrideAbstractProductBCreateProductB()
{
returnnewProductB2();
}
}
//抽象产品 A,产品族中一个成员
abstractclassAbstractProductA
{
}
//抽象产品 B,产品族中一个成员
abstractclassAbstractProductB
{
publicabstractvoidInteract(AbstractProductAa);
}
//具体产品 A1
classProductA1:AbstractProductA
{
}
//具体产品 B1
classProductB1:AbstractProductB
{
publicoverridevoidInteract(AbstractProductAa)
{
(().Name+
"interactswith"+().Name);
}
}
//具体产品 A2
classProductA2:AbstractProductA
{
}
//具体产品 B2
classProductB2:AbstractProductB
{
publicoverridevoidInteract(AbstractProductAa)
{
(().Name+
"interactswith"+().Name);
}
}
//客户端,使用环境
classClient
{
privateAbstractProductAAbstractProductA;
privateAbstractProductBAbstractProductB;
//构造,注意通过构造传入抽象工厂
publicClient(AbstractFactoryfactory)
{
AbstractProductB=();
AbstractProductA=();
}
publicvoidRun()
{
(AbstractProductA);
}
}
}
输出结果为:
ProductB1interactswithProductA1
ProductB2interactswithProductA2
生活中的实例:
这个生活中的实例代码演示了一个电脑游戏,在游戏中建立不同的动物世界会使用不同的工
厂。虽然创建动物的大陆工厂是不同的,但是动物之间的相互关系保持不变。
RealworldcodeusingAbstractFactoryinC#
usingSystem;
namespace.
{
classMainApp
{
publicstaticvoidMain()
{
//创建非洲大陆
ContinentFactoryafrica=newAfricaFactory();
AnimalWorldworld=newAnimalWorld(africa);
();
//创建美洲大陆
ContinentFactoryamerica=newAmericaFactory();
world=newAnimalWorld(america);
();
//等待用户输入
();
}
}
//抽象工厂
abstractclassContinentFactory
{
//创建食草动物
publicabstractHerbivoreCreateHerbivore();
//创建食肉动物
publicabstractCarnivoreCreateCarnivore();
}
//具体工厂 1
classAfricaFactory:ContinentFactory
{
publicoverrideHerbivoreCreateHerbivore()
{
//返回牛羚
returnnewWildebeest();
}
publicoverrideCarnivoreCreateCarnivore()
{
//返回狮子
returnnewLion();
}
}
//具体工厂 2
classAmericaFactory:ContinentFactory
{
publicoverrideHerbivoreCreateHerbivore()
{
//返回野牛
returnnewBison();
}
publicoverrideCarnivoreCreateCarnivore()
{
//返回狼
returnnewWolf();
}
}
//抽象产品 A
abstractclassHerbivore
{
}
//抽象产品 B
abstractclassCarnivore
{
//交互关系,食肉动物可以吃掉食草动物
publicabstractvoidEat(Herbivoreh);
}
//具体产品 A1
classWildebeest:Herbivore
{
}
//具体产品 B1
classLion:Carnivore
{
publicoverridevoidEat(Herbivoreh)
{
//吃掉牛羚
(().Name+
"eats"+().Name);
}
}
//具体产品 A2
classBison:Herbivore
{
}
//具体产品 B2
classWolf:Carnivore
{
publicoverridevoidEat(Herbivoreh)
{
//吃掉野牛
(().Name+
"eats"+().Name);
}
}
//客户端
classAnimalWorld
{
privateHerbivore_herbivore;
privateCarnivore_carnivore;
//通过构造器传入具体工厂
publicAnimalWorld(ContinentFactoryfactory)
{
_carnivore=();
_herbivore=();
}
publicvoidRunFoodChain()
{
_(_herbivore);
}
}
}
RealworldcodeusingAbstractFactoryinC#
输出的结果为:
LioneatsWildebeest
WolfeatsBison
什么情况下使用抽象工厂:
文献【GOF95】指出,在以下情况下应当考虑使用抽象工厂模式:
1.一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模
式都是重要的。
2、这个系统的产品有多于一个产品族,而系统只消费其中某一个族的产品(上面这一条叫做抽
象工厂模式的原始用意。)
3、同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
4、系统提供一个产品类的库,所有的产品以同样的接口实现,从而使客户端不依赖于实现。
实际项目举例
现在需要创建分属于不同操作系统的视窗构件。比如命令按钮(Button)与文本框(Text)等都
是视窗构件,在 UNIX 系统的视窗环境和 Windows 操作系统的视窗环境中,这两个构件有不同的
本地体现,它们的细节也有所不同。在每一个操作系统中,都有一个视窗构件组成构件家族。在
这里就是 Button和 Text组成的产品族。而每一个视窗构件都构成自己的等级结构,由一个抽象
角色给出抽象的功能描述,而由具体子类给出不同操作系统的具体实现,如下图所示。
可以发现在上面的产品类图中,有两个产品的等级结构,分别是 Button等级结构和 Text等级结
构、同时有两个产品族,也就是 UNIX 产品族和 Windows 产品族。UNIX 产品族由 UnixButton 和
UnixText产品组成;而 Windows产品族由 WinButton和 WinText产品构成。
系统对产品对象的创建需求由一个工厂的等级结构满足,其中有两个具体工厂角色,即
UnixFactory 和 WinFactory。UnixFactory 对象负责创建 Unix 产品族中的产品,而 WinFactory
对象负责创建 Windows产品族中的产品。这就是抽象工厂模式的应用,抽象工厂模式的解决方案
如下图所示。
显然,一个系统只能够在某一个操作系统的视窗环境下运行,而不能够同时在不同的操作系统上
运行。所以,系统实际上只能消费属于同一个产品族的产品。这个案例实际上也正是抽象工厂模
式的起源。实现的代码如下。
ProjectcodeusingAbstractFactoryinC#
输出结果为:
ProjectcodeusingAbstractFactoryinC#
//抽象工厂起源案例
usingSystem;
namespace.
{
classMainApp
{
publicstaticvoidMain()
{
//创建 Unix使用环境
OSFactoryunix=newUnixFactory();
Clientclient=newClient(unix);
();
//创建 Windows使用环境
OSFactorywindows=newWinFactory();
client=newClient(windows);
();
//等待用户输入
();
}
}
//抽象工厂
abstractclassOSFactory
{
//创建按钮构件
publicabstractButtonCreateButton();
//创建文本框构件
publicabstractTextCreateText();
}
//具体工厂 1
classUnixFactory:OSFactory
{
publicoverrideButtonCreateButton()
{
//返回 Unix下的 Button
returnnewUnixButton();
}
publicoverrideTextCreateText()
{
//返回 Unix下的 Text
returnnewUnixText();
}
}
//具体工厂 2
classWinFactory:OSFactory
{
publicoverrideButtonCreateButton()
{
//返回 Windows下的 Button
returnnewWinButton();
}
publicoverrideTextCreateText()
{
//返回 Winodws下的 Text
returnnewWinText();
}
}
//抽象产品 A
abstractclassButton
{
}
//抽象产品 B
abstractclassText
{
//交互关系
publicabstractvoidInteract(Buttonb);
}
//具体产品 A1
classUnixButton:Button
{
}
//具体产品 B1
classUnixText:Text
{
publicoverridevoidInteract(Buttonb)
{
(().Name+
"interactwith"+().Name);
}
}
//具体产品 A2
classWinButton:Button
{
}
//具体产品 B2
classWinText:Text
{
publicoverridevoidInteract(Buttonb)
{
(().Name+
"interactwith"+().Name);
}
}
//客户端
classClient
{
privateButton_button;
privateText_text;
//通过构造器传入具体工厂
publicClient(OSFactoryfactory)
{
_button=();
_text=();
}
publicvoidRun()
{
_(_button);
}
}
}
UnixTextinteractwithUnixButton
WinTextinteractwithWinButton
小结
抽象工厂模式是一个在实际项目中应用比较多的设计模式之一,抽象工厂模式面对的问题是多个
产品等级结构的系统设计,运用抽象工厂模式的关键在于如果把创建产品的职责交给工厂去完成,
希望大家在把握住工厂模式原型的基础上尽量的考虑到应用,形成一种思维上的定势。
这一篇我将向大家讲解建造者(Builder)模式。在上一篇文章中我们主要学习了抽象工厂
(AbstractFactory)模式,抽象工厂模式主要解决对不同等级结构的产品的创建工作,主要关
注的是创建哪一批产品的问题,而本文所讲的建造者模式主要是解决对于一个产品如何分部创建
的问题,这是对于建造者模式的最初描述。同样,这篇文章主要分为两大部分来讲解,第一部分
我会对建造者模式的原型进行详细的说明,第二部分会对建造者模式如何解决具体问题进行探讨。
建造者模式的原型
描述:
在软件系统中,有时候面临一个"复杂对象"的创建工作,其通常由各个部分的子对象用一定算法
构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合到一
起的算法却相对稳定。
建造者模式是对对象的创建模式。建造者模式可以将一个产品的内部表象与产品的生成过程分隔
开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。建造者模式利用一个导演
者对象和具体建造者对象一个一个地建造出所有的零件,从而建造出完整的产品对象。建造者模
式将产品的结构和产品的零件建造过程对客户端隐藏起来,把对建造过程进行指挥的责任和具体
建造零件的责任分隔开来,达到责任划分和封装的目的。
意图:
将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
模式原型 UML:
建造者模式涉及到以下角色:
1、抽象建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。
此接口中一般至少规定两个方法,一个是创建部分的方法,例如 BuilderPart,另一个是返回结
果的方法,例如 GetProduct,以约束具体建造者实现。
2、具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它
们在应用程序的调用下创建产品的实例。这个角色产品实现了抽象建造者接口,主要完成分部创
建产品并提供产品对象的实例。
3、导演者(Director)角色:顾名思义,就是具体指挥使用哪个具体创造者来完成产品的创建,
是创建工作的调用者。但是,导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识
的是具体建造者角色。
4、产品(Product)角色:产品角色就是建造中的复杂对象。一般只有对于复杂对象的创建才使
用建造者模式,一个系统中会出现多于一个的产品类,而这些产品类并不一定有共同的接口,可
能完全不关联,这时就需要提供多套抽象和具体的建造者来完成不一致的产品的创建或者是采用
一个统一接口来标识产品,我个人推荐前者。请大家注意,这里的产品只是一个产品类,不存在
继承关系,所以也就没有像抽象工厂中的那种客户端依赖抽象的说法了。
模式原型代码:
BuilderpatterncodeinC#
输出结果为:
ProductParts-------
PartA
PartB
ProductParts-------
PartX
PartY
生活中的实例:
这个小例子向大家展示了一辆车是如何利用建造者模式被创建的,注意商店(这里就是导演者)
是如何利用 VehicleBuilders按照一定的顺序创建一辆车的。
RealworldcodeusingAbstractFactoryinC#
输出结果为:
---------------------------
VehicleType:Scooter
Frame:ScooterFrame
Engine:none
#Wheels:2
#Doors:0
---------------------------
VehicleType:Car
Frame:CarFrame
Engine:2500cc
#Wheels:4
#Doors:4
---------------------------
VehicleType:MotorCycle
Frame:MotorCycleFrame
Engine:500cc
#Wheels:2
#Doors:0
什么情况下使用建造者:
1、需要生成的产品对象有复杂的内部结构。每一个内部成分本身可以是对象,也可以仅仅是一
个对象的组成部分。
2、需要生成的产品对象的属性相互依赖。建造者模式可以强制实行一种分步骤进行的建造过程,
因此,如果对象的一个属性必须在另一个属性被赋值之后才可以被赋值,使用建造者模式便是一
个很好的设计思想。
3、在对象创建过程中会使用到系统中的其他一些对象,这些对象在产品对象的创建过程中不易
得到。
实际项目举例
现在我们需要在一个在 PC 游戏中构建房子的程序。可以构建的房子有多种样式,但是房屋的结
构是相对稳定的,每个房子都有地基,墙面,房间,门,窗,地板等,同时也存在一定的构建顺
序(必须先构建地基,这也就是产品类的组成部分之间的依赖关系),房子的具体风格可能包括
很多种,例如居民住房,存放货物的仓储房等。由于房子的结构稳定,变化的只是房子的组成部
分,这时我们可以考虑采用建造者模式来完成具体房屋的创建工作,即创建房屋和使用房屋责任
的分离,房屋的构建与其表示相分离。同样的建造过程可以创建不同的表示(居民住房,存放货
物的仓储房等)。
本案例中的类与建造者模式中的角色的对应关系:
BuildingBuilder(房屋建造者)——Builder(抽象建造者)
Architect(建造师)——Director(导演者)
HousingBuilder(住房建造者)——ConcreteBuilder1(具体建造者 1)
StorageBuilder(仓储房建造者)——ConcreteBuilder2(具体建造者 2)
Building(房子)——Product(产品)
实现的代码如下:
ProjectcodeusingAbstractFactoryinC#
输出结果为:
BuildingType:Housing
Base:HousingBase
Wall:HousingWall
#Windows:2
#Doors:2
#Rooms:3
Floor:3*20m2
BuildingType:Storage
Base:StorageBase
Wall:StorageWall
#Windows:0
#Doors:1
#Rooms:0
Floor:1000m2
小结
1、Builder模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是稳定的,而复
杂对象的各个部分则经常变化。
2、Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分
步骤构建算法”的需求变动。
3、AbstractFactory模式解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求
变化。Builder械通常和 Composite模式组合使用