乱砍设计模式之五
最后更新于:2022-04-01 14:36:53
FACTORY 与 ABSTRACT FACTORY模式 —— 号令秦姬驱赵女,艳李秾桃临战场
junguo
这一次,将集中讲一下创建型模式,主要以Factory和Abstract Factory模式为主。按上次的惯例,还是以例子开始。这次的例子仍以战场和美女为例,呵呵,和战场及美女死磕上了。采用这样的例子,只是想帮助大家更好的记忆,我最简化自己的例子,以帮助大家认识模式之形。写完这个系列,也许会尝试写一些具体实例的模式。不过这样的书其实也有,《道法自然》就是一本优秀的书,有兴趣可以找来看看。还是来看我们要用到的例子:
恒王好武兼好色,遂教美女习骑射。
秾歌艳舞不成欢,列阵挽戈为自得。
眼前不见尘沙起,将军倩影红灯里。
叱咤时闻口舌香,霜矛雪剑娇难举。
丁香结子芙蓉绦,不系明珠系宝刀。
战罢夜阑心力怯,脂痕粉渍污鲛鮹。
明年流寇走山东,强吞虎豹势如蜂。
王率天兵思剿灭,一战再战不成功。
腥风吹折陇头麦,日照旌旗虎帐空。
青山寂寂水澌澌,正是恒王战死时。
雨淋白骨血染草,月冷黄沙鬼守尸。
纷纷将士只保身,青州眼见皆灰尘。
不期忠义明闺阁,愤起恒王得意人。
恒王得意数谁行,姽婳将军林四娘。
号令秦姬驱赵女,艳李秾桃临战场。
秀鞍有泪春愁重,铁甲无声夜气凉。
胜负自然难预定,誓盟生死报前王。
贼势猖獗不可敌,柳折花残实可伤。
魂依城郭家乡近,马践胭脂骨髓香。
星驰时报入京师,谁家儿女不伤悲。
天子惊慌恨失守,此时文武皆垂首。
何事文武立朝纲,不及闺中林四娘。
我为四娘长叹息,歌成余意尚彷徨。
这首诗取自《红楼梦》第七十八回,该诗的起因如下。贾政和他的幕僚聊天的时候讲到一个故事。当年有一个哥们被封为恒王,镇守青州,此君好色成性,闲暇的时候还好弄武摆阵。自己玩的不过瘾,把他选来的美女也拉了进来。每天看着这帮美女舞刀弄枪,还时常进行一些军事演习。在众多美女中有一个叫林四娘的,最为漂亮,而且武功最好,恒王让她做了众人的统领,并号为姽婳将军。不想第二年,青州发生了农民起义,此君没有把农民军当回事,确把自己当回事。亲自带兵去征剿,结果打了两战,输了两战,损兵折将,还将自己搭了进去,成了烈士。消息传到青州城中,马上乱了套。用指头都可以想象得到,这样的一个王,手下能有什么人才?众多文武官员,不想着退敌护城,有的想着如何逃跑,有的想着不如献城。这样的情形激怒了林四娘,她集合起了所有女将,对众将言到:“你我皆向蒙王恩,戴天履地,不能报其万一。今王既殒身国事,我意亦当殒身于王。尔等有愿随者,即时同我前往;有不愿者,亦早各散。”结果没有一个人愿意离开,林四娘带人连夜出城杀向贼营里面,刚开始人家没防备,被她们杀了几个人。后来一看是都是女人,就一起围了上来,把众女将杀的一个不剩。女为悦已者容变成了女为悦已者死,我在想为了这么一个王,她们死的值吗?这帮农民起义军也真能下的去手,不过想想好多事情都不可理喻。当年八路军对日本鬼子采取了优待俘虏的政策。对自己人确毫不含糊,反拖肃反的时候,很多青年男女被秘密处决。为了节省子弹,还研发了很多土制方法,如直接把人填到井里(《白鹿原》里的共产党员白玲就是这么死的),还有在人身上通上较弱的电流慢慢折磨至死(看过《绿色奇迹》没有?看过的话,回想一下执行电刑时由于没有往海绵中加水而被烧焦的哪个犯人的死状,你就能想象到当时的惨烈程度了)。所以我记住了我们《马哲》老师的一句话:千万不要去沾什么主义。接着上面的故事,后来消息报了上去,朝廷重新派人来镇压农民起义,这次来人不象恒王那样无能,剿灭了这帮农民起义军。
众幕僚听着直流哈喇子,羡慕恒王羡慕的要死。文人的酸劲上来要做首诗来应和。贾政把宝玉,贾环和贾兰也叫了过来,让他们也一人做一首。宝玉写下了这首古体长诗《姽婳词》。《红楼梦》是一本伟大的作品,将中国的诗书琴画,饮食建筑尽融其中,每个人在其中都可以找到自己感兴趣的部分。
你应该猜到了,我们此次的例子是女将军的战争场面。战争一般说来肯定离不开男人,所以男性将军也需要保留。但女将军的作战处理可能和男性并不相同,所以我们会她们单独的提供一个类,而女将军和男将军会有一个共同的接口。先来看类图:
假设在没有继承时有这么一个函数:
~~~
void War()
{
General *pGeneral = new General();
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
delete pGeneral;
}
~~~
在有了继承的情况下,该函数就可能会变成以下的情况:
~~~
void War(string strType)
{
General *pGeneral ;
if ( strType == “GirlGeneral”)
{
pGeneral = new GirlGeneral();
}
else if ( strType == “ManGeneral”)
{
pGeneral = new ManGeneral();
}
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
delete pGeneral;
}
~~~
想象一下,创建对象的地方很可能并不只一处。那么你就可能在很多地方看到类似的代码,想象一下如果我们再增加一些少年将军,神仙将军一类的基类,那你是不是需要一个一个找到这样的函数去改?(好多时候遇到这样的情况,开发人员怪用户提的要求变态,确很少考虑自己的程序扩展性不强。)为了避免这种情况,最简单的方法就是将类的创建过程独立为一个单独的类,如下:
~~~
class GeneralFactory
{
public:
static General *CreateGeneral(string strType)
{
if ( strType == “GirlGeneral”)
{
pGeneral = new GirlGeneral();
}
else if ( strType == “ManGeneral”)
{
pGeneral = new ManGeneral();
}
}
};
~~~
这样我们的创建代码被放到了一个统一的地方,在类进行扩展的时候,就可以方便的进行修改了。也避免了代码的冗余。不过这个方法并没有归于GOF的设计模式中,我们以下将谈Factory模式。
再考虑一个问题,我们的War是出现在一个类中,如我们有一个军队类,而军队分为男军和女军。男军的将领肯定是男将军,而女军的将领肯定是女将军。而且General类只会在军队类中用到。那我们还有没有必要写一个创建类呢?答案是否定的。我们先来看类图:
那么这时候,我们就可以将CreateGeneral放到Army类中,而通过它的子类来实现它。好的,我们还是先看看具体的代码,这样有助于你理解。
~~~
#include
#include
using namespace std;
class General
{
public:
virtual void Ready() = 0;
virtual void Advance() = 0;
virtual void Assault() = 0;
};
class GirlGeneral : public General
{
private:
string m_strName;
public:
GirlGeneral(string strName):m_strName(strName){}
void Ready()
{
cout << " 女将军 " << m_strName << "正在准备!"<< endl;
}
void Advance()
{
cout << " 女将军 " << m_strName << "正在前进!"<< endl;
}
void Assault()
{
cout << " 女将军 " << m_strName << "正在攻击!"<< endl;
}
};
class ManGeneral : public General
{
private:
string m_strName;
public:
ManGeneral(string strName):m_strName(strName){}
void Ready()
{
cout << " 将军 " << m_strName << "正在准备!"<< endl;
}
void Advance()
{
cout << " 将军 " << m_strName << "正在前进!"<< endl;
}
void Assault()
{
cout << " 将军 " << m_strName << "正在攻击!"<< endl;
}
};
class Army
{
public:
void war(string strName)
{
cout << "战斗步骤" << endl;
//此处调用虚函数,事实上调用的是子类的方法
General *pGeneral = CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
//此处定义一个创建Geraral的类,而不具体实现
virtual General* CreateGeneral(string strName) = 0;
};
class GirlArmy : public Army
{
public:
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
};
class ManArmy : public Army
{
public:
General *CreateGeneral(string strName)
{
return new ManGeneral(strName);
}
};
int main(int argc, char* argv[])
{
GirlArmy gArmy;
gArmy.war("姽婳将军");
ManArmy mArmy;
mArmy.war("笨蛋将军");
return 0;
}
~~~
代码比较简单,无需太多解释,需要注意的就是在Army类中注释过的地方就可以了。
下面我们来看GOF对于Factory模式的定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method使一个类的实例化延迟到其子类。
GOF书中提供的一个例子机具代表性,是Factory的典型用法,我们来看看。
这是我们在文档视中看到的典型做法,在MFC中也可以找到。Application并不是自己创建文档,而是通过在子类中重载CreateDocument来实现。Factory模式,我们的讲解先到此。
我们再来考虑一个问题:我们的Army类存在与General同样的问题,生成对象的时候需要考虑到它的类型。那我们是否需要象上面提到的GeneralFactory一样,为它也提供一个类厂呢?接着扩展我们的想象,由于女将的加入,她们使用的兵器,坐骑以至战争场景等内容也许都需要做相应的扩展,那么我们是否需要这么多类都提供一个类厂呢?我们可以注意到所有这些扩展的类都有相似的功能都是为女性提供的,那么我们就可以给它们提供一个统一的类来实现创建过程。该方式就是Abstract Factory模式,我们首先来看看该模式。
Abstract Factory模式的定义是:提供一个创建一系列产品或者相互依赖对象的接口,而无需调用处指定具体的类。接着看看Abstract Factory的类图:
图中AbstractFactory是一个抽象接口,定义了创建不同产品的虚拟函数。而ConcreteFactory1和ConcreteFactory2是我们用到的具体的生成产品的类,图上标的虚线表示它们具体要生成的那个类的对象。而AbstractProcutA和AbstractProcutB是抽象产品接口,相当于我们的Army和General接口。而ConcreteProcutA1和ConcreProcutB1是具体的产品类,我们的例子中相当于GirlArmy和GirlGeneral。而ConcreteProcutA2和ConcreProcutB2就相于ManArmy和ManGeneral了。
接着看我们的例子,此处就不画类图了。直接看看代码:
~~~
class AbstractFactory
{
public:
virtual General* CreateGeneral() = 0;
virtual Army* CreateArmy() = 0;
};
class GirlFactory : public AbstractFactory
{
public:
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
Army *CreateArmy()
{
return new GirlArmy();
}
};
class ManFactory : public AbstractFactory
{
public:
General *CreateGeneral(string strName)
{
return new ManGeneral(strName);
}
Army *CreateArmy()
{
return new ManArmy();
}
};
~~~
这样在我们的例子中的函数:
~~~
void war(string strName)
{
cout << "战斗步骤" << endl;
General *pGeneral = CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
~~~
可能变为
~~~
void war(AbstractFactory *pFactory,string strName)
{
cout << "战斗步骤" << endl;
General *pGeneral = AbstractFactory ->CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
~~~
Abstract Factory在现实开发中用的比较多的情况是在处理数据库的时候,如果你的程序需要同时支持oracle和SQL Server数据库,那样几乎所有的数据操作类都需要两套代码。那么使用Abstract Factory模式,只需通过配置文件生成不同的创建函数,就可以实现不同数据库的实现了。
事实上Abstract Factory相当于Factory模式的扩展,都是基类定义一个创建对象的方法,而由子类来实现。
Abstract Factory模式也可以使用PROTOTYPE(原型)模式来实现,PROTOTYPE的定义是:用原型实例来创建对象的类。说白了就是通过拷贝构造函数来实现新类的创建。这里就不多讲了。
还有Builder模式,其实和Abstract Factory很相似,但它强调的内容不同,它强调的是一步一步要创建对象,也就是说它会帮助对象创建一些内容。而Abstract Factory是直接返回对象。
接着考虑,我们想把我们的工厂类作成一个单件类型,这样的话,由于工厂类只有一个对象,我们就容易对工厂对象所要创建的对象做些控制,如可以限定对象的生成数,控制对象的生成时机等。我们不能象上节一样直接把CreateGeneral定义成static型,因为这样就没有动态的特性。那用什么方法呢?还是看代码:
~~~
class GirlFactory : public AbstractFactory
{
private:
static GirlFactory *instance;
GirlFactory(){}
public:
static GirlFactory* GetInstance()
{
if ( instance == NULL )
{
instance = new GirlFactory();
}
return instance;
}
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
Army *CreateArmy()
{
return new GirlArmy();
}
};
GirlFactory *GirlFactory::instance = NULL;
~~~
我们的类工厂中,构造函数GirlFactory被设置成了私有成员函数,这样只有它本身才能调用。我们再填加一个静态成员变量instance,这样保证了只有一个对象。而后再定义一个静态函数GetInstance,它返回所需要的类对象。
这样我们对类对象的调用就成了以下的形式:
GirlFactory *pGirlFactory = GirlFactory::GetInstance();
好了,这就是单件模式了。这次就先讲这么多了。
参考书目:
1,设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2,Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3,道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4,红楼梦(脂批汇校本) —— 网上找到的电子档