`
soboer
  • 浏览: 1303211 次
文章分类
社区版块
存档分类
最新评论

【GOF设计模式之路】-- Factory

 
阅读更多

自从开始工作,就感觉精力相比在大学时有很大幅度的下降。大二那一年精力最旺盛,自从大二结束开始工作到现在,两年时间,似乎精力都已经不受自己控制了。如果对一些技术研究工作不是很感兴趣,下班之后基本上到晚上10点左右就想睡觉。工作两年加上大二的一年,一直到现在都坚持每天必须有新的东西进入脑子,进步倒是明显感受到了,但真担心现在的精力还能坚持几年的技术研究。但愿不要像大家说的到了30岁以后就不适合做技术研究了,我个人觉得人活着就是为了做自己喜欢的事,那么就等到自己不再喜欢技术研究时再考虑转型吧。这个过程可能是一辈子,也可能是短短的几年,因人而异吧,先走好当前的路!come on!!

不知不觉距写前一篇Singleton时已经有一个多月了,一是忙,二是精力不足,三是加上烦心的事就完全没心情写博了。有时候一直在想人活着是为了啥,似乎没有明确的答案。可能我还需要磨练吧。前一篇Singleton,就有一些朋友说写得比较复杂,在实际中基本不会搞得那么复杂,我之前也说过,设计模式并不需要严格遵循,可以根据实际情况做一些具体特殊的优化和演化,所以复杂的情况是应付复杂的需要,简单的是为了简单的需要。我们也没有必要为了简单的需要而使用复杂的规则,反之亦然。也就是所谓的灵活应对吧,更何况本系列的设计模式的示例是用C++解释的。更好玩的是有朋友的评论Singleton:“这是我见到过写得最好的Singleton”,这让我倍感欣慰,可这时又有朋友回复这个朋友的评论说:“是写得最多才对!”。欣慰之下又多了一些反思,难到我写的Singleton真的没有被参考的价值?其实,反过来想,我写博并不是为了最好最牛,只是习惯性写写,对自己有帮助,做到自己的最好即可。当然既然写出来了,也尽量在能力范围内不误导别人,也非常乐意接受别人批评。批评对于我来说是一面很不错的镜子。

好了,回归正题,本篇介绍工厂模式(Factory),在本文中,会介绍三种工厂模式相关的设计,即:(简单工厂)Simple Factory、(工厂方法)Factory Method(抽象工厂)Abstract Factory。虽然Simple Factory不是GOF的成员,但它在实际中也很常见和实用,通常作为Factory的一种特殊模式。本系列是以GOF设计模式为标题,但并不表示所有的模式都得在GOF的范围内,也即所谓的灵活吧!

简单工厂(Simple Factory)

还是以最简单实用的简单工厂开始,也好逐步加深印象,同时便于理解。所谓简单工厂(Simple Factory),自然也就是简单设计为主,直截了当。举个例子,例如网络游戏,在游戏世界里有很多个对象(Object),例如玩家(Player)、NPC、怪物(Monster)等。这些对象就好比一个个的产品,它们属于一个产品大类。而这一个个的产品又是由某个工厂所制造出来的,这里的工厂就可以是对象管理器。我们用户在想要创建一个对象时,只需要告诉对象管理器(工厂)我们需要什么类型的对象(产品),然后就把创建(生产)对象(产品)的任务托付给了对象管理器(工厂)。而我们只需要使用创建(生产)出来的对象(产品),专注于其商业逻辑。

由此我们可以构建一个简单工厂,可以有两种形式,一是每一种对象都使用一个创建函数,二是所有对象都使用同一个创建函数,由参数区分创建的对象的种类。在实际中,往往偏向第二种形式。示例代码如下:

调用如下:

如上,IObject即是对象(产品)基类,所有对象(产品)都继承于它。CPlayer、CNpc和CMonster则是具体的对象(产品)类。CObjectManager则为对象管理器(简单工厂),它负责创建(生产)CPlayer、CNpc和CMonster等具体的对象(产品)。在上例中,我使用了IID这种形式来确定创建什么类的实体,IID可以理解为对象唯一ID。对象管理器在创建对象时可以根据唯一ID进行区分,如main函数中的pObj1和pObj2,另外,在很多场合都是使用的基类指针进行逻辑处理,在特定的时候会需要动态转换以确定这个IObject*是什么子类型(当然设计上不推荐这么做)。因此在IObject类里增加了DynamicCast函数,此函数可根据参数的IID值,返回具体的子类,如果没有找到则返回NULL。VCAST是一个虚函数,是为了向下定位查找子类是否有IID与其参数的IID相同,若没有则返回NULL(如上例中的DynamicCast( IID_MONSTER )则会失败而返回NULL)。在上例中,IObject只被继承了一层,所以当nIID与子类的IID匹配时(如CPlayer对应IID_PLAYER),m_iIID == iIID始终是成立的,VCAST也就不会再调用,显得作用不大,但是当有多层继承时,而IObject类只能保存起子类继承关系中某一层的IID,此时,m_iIID == iIID就不一定成立了,此时VCAST的作用就明显了,例如CPlayer还有子类,则CPlayer的子类的VCAST应该设计为:

virtual IObject* VCAST( const int iIID){ return ( iIID == IID_PLAYER|| iIID == IID_SUBPLAYER) ? this : IObject::VCAST( iIID); }

因为有多层继承关系时,可允许子类不重新修改IObject的m_iIID的值,所以判断了本身类的IID和基类的IID。当IID不等时,则直接调用IObject::VCAST( iIID)返回到IObject基类中,将由它统一决定返回值,本文中统一返回NULL。(VCAST函数的大部分逻辑都是一样的,可以考虑使用宏定义)

其实这个DynamicCast和VCAST组合也就起到了dynamic_cast的作用,dynamic_cast在效率上要低一些,它是通过RTTI描述符进行定位,而DynamicCast通过VCAST虚函数定位子类的VCAST。在编译时就已经决定了调用DynamicCast时该调用虚函数表里哪个虚函数,dynamic_cast的转换是具体存在的。(PS:在实际中并不推荐使用dynamic_cast和DynamicCast,在抽象一层应该做好接口,避免直接面对具体的对象类型)

如果对这个流程不清楚,可以自己写写再跟踪一下,就能有所体会了。

如果将对象的创建使用不同的创建函数,如上面所说的第一种形式,则CObjectManager可以设计为:

这样在使用时,就只能分开创建了,我个人偏好传递参数的方式进行创建。

Simple Factory有时有称作静态工厂,静态工厂也是简单工厂,与上面示例的不同之处在于工厂类的创建函数是静态的,以至于在创建对象时,可以不用创建工厂对象,如下:

这种方式在实际的复杂的继承情况中可能并不适用,它将创建函数设计为静态,便不能让其子类重写和扩展了。

上例的输出结果为:

Player Run
Monster Run
Player Run

最后一次DynamicCast( IID_MONSTER)失败了,返回NULL,不会输出。

为了更直观的总结一下简单工厂模式,如下图:

图1 简单工厂模式(Simple Factory)

好了,简单工厂也就差不多了,小结一下:

其优点:

一是将工厂中的各个产品的创建都集中到一起,便于管理与维护,能够减少以后修改的工作量。二是将主体逻辑与抽象逻辑分离,工厂负责抽象及创建出产品,用户则只需要负责主体逻辑,分工明确且非常协调。(这两点也可归结于整个工厂模式的好处)

其缺点:

对未来的扩展性适应不是非常良好,它对修改不封闭,即如果要新增加一个产品,则需要修改工厂的实现,这违反了开闭原则(OCP)。

工厂方法(Factory Method)

前面谈到Simple Factory的缺点时,发现它违反了开闭原则,每增加一个产品就得修改工厂创建函数的逻辑实现。例如要增加一个新的对象CItem(游戏中的道具),此时就得修改CreateObject函数,以后每增加一个对象都得这样做,CObjectManager就不能对修改封闭。

那么,如何才能解决这个问题呢,想想面向对象,想想多态,于是Factory Method模式应运而生。我们可以将工厂抽象了,然后再继承一系列的子工厂。例如某某集团公司,这个集团的名称可以只是挂一个名,而此集团下可以有很多个子公司,每个子公司就负责做具体的实事。而集团总部就只需要支配即可。每个子公司的人事、财政等运作都是独立的,互不干涉。假如要将集团战略发展到一个新的领域,只需要新建一个子公司或购买一个公司作为子公司。这也就是所谓的修改封闭。再例如某餐厅,最开始是只请了一个厨师(简单工厂),这个厨师只会做川菜,对于此刻餐厅的规模来说,已经完全足够了。随着这名厨师的厨艺被大家认可之后,餐厅的生意也越来越好了。需求也不断增加,更有外地的顾客光临,餐厅为了能够让外地的顾客能够尝到家乡的味道,于是“做出了一个艰难的决定”,要让厨师学习做异地菜肴,如鲁菜。可是厨师师傅又要下厨,又要学习,他都一把年纪了,哪儿有那么多精力呢,更何况这是一个熟能生巧的活。餐厅老板也能体会厨师师傅的苦,于是提升他为厨师总管(没办法,他资格最老嘛),然后又招聘了很多个厨师,有川菜厨师、鲁菜厨师、湘菜厨师等等。而厨师总管以后就不用下厨了,他只需要直接面对餐厅老板,下达命令。这样既形成培养模式,又能不让厨师总管学习做各种地域的菜肴了。需要新的地域菜肴就直接招聘新的厨师,而原来的厨师也不需要涉足太广。于是餐厅的规模也就越做越大了,老板成了亿万富翁。。。

了解了整体结构和流程之后,Factory Method和Simple Factory的区别其实不明显,唯一的区别只是将工厂抽象化了,然后再创建一系列的子工厂。CreateObject还是同样的逻辑,只是此刻的CreateObject不能为static了,它要被子工厂重写。还是先看代码吧,如下:

抽象工厂及子工厂:

用户:

如上,我们将原先的CObjectManager提升(抽象)为集团公司(抽象工厂):IObjectManager。而CPlayer、CNpc和CMonster还是作为同一个对象管理器(工厂)的对象(产品),由CFightObjManger(可战斗对象管理器)管理和创建(生产)。新增加的两个对象(产品):CItem(道具)和CBuilding(建筑物)则由新的CRegionObjManager(场景对象管理器)管理和创建(生产)。以后再增加新的对象则不需要改变CFightObjManger和CRegionObjManager管理器(工厂)。实现了修改封闭,符合OCP。此时,你可能发现有一个问题,CFightObjManger和CRegionObjManager明显是两大类,意思就是如果将CItem和CBuilding放到CFightObjManager里并不合适,反过来将CPlayer、CNpc和CMonster放到CRegionObjManager里创建也不怎么合适。这样就又可能出现违反OCP的情况,即如果CFightObjManger和CRegionObjManager已经存在,而新增的对象(产品)在类型上是应该属于它们两者中某一个工厂的,此刻创建新的管理器(工厂)就显得没必要。此时就还是得修改CFightObjManger或CRegionObjManager的实现。因此,要尽量解决这个问题,就得在设计工厂时,尽量把以后需要的产品都想到最全面,减少修改的次数,尽量实现修改封闭(PS:所以架构师也不是那么好做的-.-)。你总不可能在实际研发中把各个工厂都命名为Factory1,Factory2...哟!-_-!!!

这样看来,具体的工厂子类要尽量不被修改就得看设计者的思维了,而Factory Method对于顶层的抽象工厂(IObjectManager)来讲,是符合OCP的,它不负责具体实现,只需要发送命令,好比前面的集团公司总部和厨师总管,他们完全可以“坐吃山空”。

上面的代码很简单,就不具体解释了,输出结果为:

NPC Run
Building Run

Factory Method还可以做进一步演化,可以将所有的产品对象聚集在一起,例如有一个Object容器,当用户需要创建新的Object时,首先到容器里查找是否已经存在,如果存在则直接返回,不存在则创建一个此类型的Object,然后加到容器里。这样便能够循环利用,这也就是享元模式的特色。以后待谈及到享元模式时再具体讨论吧。在实际中,通常是多种模式相结合,已达到程序的需求。

工厂方法(Factory Method)的流程图示如下:

图2 工厂方法模式(Factory Method)

工厂方法就差不多这么多了,小结一下:

除了拥有简单工厂的优点之外,还弥补了简单工厂的OCP问题,各个工厂相对独立,在实际中可以确定为不同的工厂类型。这样也更符合实际,想想既然产品都可以各种类型,工厂自然也可以有各种类型了。

另外,在上面的简单工厂和工厂方法里,在用户使用工厂时,应该依耐于抽象层,而不应该依耐于具体的工厂,对于产品也一样,应该依耐于抽象产品编程,而不是具体的产品,如果依耐于具体的产品就失去了工厂的意义和多态的意义。

抽象工厂(Abstract Factory

前面谈及CFightObjManger或CRegionObjManager时,谈到可能违反OCP的那种情况,也正好有了抽象工厂模式的影子,抽象工厂模式说专业一点就是解决产品族和产品等级结构之间的关系问题的。关于产品族和产品等级可以举个例子,如:游戏中的怪物和物品,通常情况下,副本中的怪物要比野外的怪物强(假设怪物也分为副本怪物和野外怪物),副本中的物品也比野外怪物爆出的物品强(假设物品也分副本和野外)。那么副本中的怪物和物品属于同一个产品族,野外的怪物和物品属于同一个产品族。而从纵向看,怪物属于一个产品等级,物品属于一个产品等级。如下图:

图3 产品族和产品等级

从图3中可知,之前的工厂方法是生产的同一个产品等级的产品,它们拥有共同的抽象基类。也就是同一系列的产品,例如CItem系列、CBuilding系列、CPlayer系列等。而Abstract Factory模式则是要创建同一个产品族的产品,例如副本产品和野外产品。同一个产品族通常不是同一系列的产品,因此,Abstract Factory包含多个产品的创建方法,从而又出现了OCP问题,当在一个产品族里增加一个新的产品时,对修改不封闭,也就是对增加产品等级的修改不封闭。只对增加一个产品族的修改封闭。这种情况也是必然,正所谓鱼和熊掌不可兼得。

再例如,我们常用的界面UI控件Button和Edit,为了表达多个平台下的界面,可分为windows、mac和unix等。于是有了WinButton、MacButton和UinxButton,WinEdit、MacEdit和UnixEdit。那么Button和Edit则分别处于两个不同的产品等级,产品族则有3个,因为有3种平台。如下图:

图4 产品等级与产品族示例

由此看来,产品族就好比将不同的产品进行捆绑式的生产,以达到特定的需求。好了,直接贴代码吧,如下:

以物品和怪物为例,我们将CItem和CMonster作为具体产品的基类,也可以进一步抽象,可视情况而定:

然后,设计抽象工厂:

用户:

如上,CFBObjManager和CFieldObjManager分别属于两个产品族,它们都拥有两个产品等级CItem和CMonster。并且IObjectManager此时有两个创建函数CreateMonster和CreateItem,它们返回的是具体产品族里的产品基类指针。

如此一来,我们便可以通过抽象工厂创建不同的产品族,例如上面的副本产品和野外产品,分别是由副本工厂和野外工厂负责生产。同样,前面聊到的餐厅,虽然规模大了,但是某天有位外地顾客突然想吃烧白。而当前只有四川风味的烧白,对于外地人可能不是很适应,于是老板下令各个地域菜的厨师都得学会做烧白,这样便能做出湘烧白、鲁烧白和粤烧白等。这样就能让外地顾客更加喜欢光临此餐厅了。但是对于餐厅来说,每个厨师都得学习烧白的做法,烧白相当于增加了一个产品等级,烧白将纳入各个地域菜产品族里。因此可谓是大动干戈啊,厨师师傅们有点小情绪,因为得学习啊,产品等级对修改不封闭。

好了,上面程序的输出结果为:

FB Monster Run
FB Item Run
Field Monster Run
Field Item Run

Abstract Factory的流程图示如下:

图5抽象工厂模式(Abstract Factory)

小结一下:

抽象工厂模式和工厂方法模式的结构差别不是很大,可以将工厂方法模式看着是抽象工厂模式的一种特殊情况,而抽象工厂模式也可看着是工厂方法模式的扩展推广。其实这之间的微妙关系在实际中能够得以体现,并且可结合使用,灵活调整。

适用性总结:

简单工厂(Simple Factory):

当一个类不知道它所必须创建的对象的类的时候。

当一个类只需要简单指定需要创建的对象的时候。

当所有产品都打算集中在某一个创建类里德时候。

工厂方法(Factory Method):

当一个类不知道它所必须创建的对象的类的时候。

当一个类希望由它的子类来指定它所创建的对象的时候。

当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

抽象工厂(Abstract Factory):

一个系统要独立于它的产品的创建、组合和表示细节,这点对所有工厂模式都很重要。

一个系统要由多个产品系列中的一个来配置时。

当你要强调一系列相关的产品对象的设计以便进行联合使用时。

当你提供一个产品类库,而只想显示它们的接口而不是实现时。

总结:

工厂模式的工厂的本质即是将创建(生产)集中化管理。产品有问题可以直接找工厂,同时在某些模式上做到对修改封闭,减少了工作量,并且更大程度上做到了复用性。再者,有了工厂,用户则不需要再管理产品的生产过程,而直接关注业务逻辑,分工明确,结构清晰。工厂模式都是以抽象的形式构建,用户接口也获得了更好的通用性。

PS:本文只是谈及了3个工厂的基本框架,在实际中可以灵活调整以供需求之用。本文的图形示例都是按照我自己的理解进行绘制的,它们看起来没有UML那么专业,我始终喜欢以一种通俗的方式去理解我看到的事物。如果你觉得这些图示不够清晰,就请参见网络上其它地方的工厂模式的UML图例吧。本文的代码只作为示范之用,在实际中往往要复杂很多,本文只是为了阐述三种工厂的基本框架。更多更好的设计还望大家指出,在此作为抛砖引玉吧。

出于水平能力问题,可能存在疏漏或错误,还望大家提出,非常感谢!本文到此结束!

【GOF设计模式之路】目录

【GOF设计模式之路】-- 开篇

【GOF设计模式之路】-- Singleton

【GOF设计模式之路】-- Factory

【GOF设计模式之路】-- Observer

分享到:
评论

相关推荐

    设计模式精解-GoF 23种设计模式解析附C++实现源码

    设计模式精解-GoF 23种设计模式解析附C++实现源码 AbstractFactory模式、Adapater模式、Composite模式、Decorator模式、Factory模式、Observer模式、Strategy模式、Template模式

    设计模式精解-GoF 23种设计模式解析

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 目 录 引 言 0.1 设计模式解析(总序) 0.2 设计模式解析后记 0.3 与作者联系 1 创建型模式 1.1 Factory模式 1.2 AbstactFactory模式 1.3 Singleton...

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码.pdf

    设计模式精解- GoF 23 种设计模式解析附 C++实现源码 目 录 0 引言 ...........................................................................................................................................

    设计模式精解-GoF 23种设计模式解析附C++实现源码.pdf

    设计模式精解-GoF 23种设计模式解析附C++实现源码 目 录 0 引言.........................................................................................................................................

    GoF 23种设计模式的详解与应用

    详细介绍GoF设计模式以及应用... 创建模式:设计模式之Factory,设计模式之Prototype(原型),设计模式之Builder,设计模式之Singleton(单态). 结构模式:设计模式之Facade(外观),设计模式之Proxy(代理),设计模式之...

    GoF Design Patterns - 设计模式 - mobi格式

    经典的由“四人帮”编著的设计模式。提供mobi格式,供kindle用户下载。 书中详细为我们介绍了23种设计模式,包括abstract factory模式等。

    设计模式 GOF 23

    本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用...

    设计模式精解(GoF 23种设计解析附C++实现源码)

    0.1设计模式解析(总序).....................................................................................................2 0.2设计模式解析后记..........................................................

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码.rar

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 目 录 0 引言 ............................................................................................................................................

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 单最常用的设计模式入门,比如AbstractFactory模式、Adapater模式、Composite模式、Decorator模式、Factory模式、Observer模式、Strategy模式、Template模式等

    设计模式体现的是一种思想,而思想则是指导行为的一切,理解和掌握了设计模式,并不是说记住了23种(或更多)设计场景和解决策略(实际上这也是很重要的一笔财富),实际接受的是一种思想的熏陶和洗礼,等这种思想...

    GoF 的 23 种设计模式

    GoF 的 23 种设计模式的分类,现在对各个模式的功能进行介绍。 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。 原型(Prototype)模式:将一...

    设计模式精解-GoF 23 种设计模式解析

    0.1 设计模式解析(总序)...............2 0.2 设计模式解析后记.......................2 0.3 与作者联系..................................5 1 创建型模式.............................................5 ...

    Simple Factory Pattern.rar【GoF的简单工厂模式(C#源码)】

    简单工厂模式实际上不属于23个GoF模式,但它可以作为GoF的工厂方法模式(Factory Method)的一个引导。 UML: <<Interface>> ConcreteProduct Creator Product <------ --------------- <----- ----------...

    ASP.NET设计模式-杨明军译(源码)

    那些以前已经体验过设计模式的读者可能希望跳过本书的第ⅰ部分,这部分介绍了gof提出的设计模式以及其他常见设计原则,包括s.o.l.i.d原则和martinfowler的企业设计模式。所有的代码示例均采用c#语言编写,但这些概念...

    uu-design-pattern:23种设计模式案例

    23种设计模式演示代码文件结构图gof23 |- creational(创建型模式) |- simplefactory 简单工厂模式案例 |- factorymethod 工厂方法模式案例 |- abstractfactory 抽象工厂模式案例 |- builder 建造者模式案例 |- ...

    设计模式之Abstract Factory模式和代码实现

    在前一节,我们介绍了Strategy模式,并使用此模式实现了一个根据角色的职业来分配技能的范例(实际也就是动态地为类分配方法)。...本文将讨论如何使用GOF的Abstract Factory抽象工厂来实现这样的角色外形设计。

    designpatterns:GoF 设计模式的简单实现

    GoF 设计模式GoF 设计模式的简单实现。 当前存储库的目的是通过简单(虚拟)示例收集《四人组》一书中介绍的模式的实现。 模式不会像在现实生活中那样混合在一起工作,例如 Strategy 通常与 Factory 等一起使用。 ...

    研磨设计模式(完整带书签).part2.pdf

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-part2

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-part4

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

Global site tag (gtag.js) - Google Analytics