设计模式-抽象工厂模式

来源:互联网 发布:sql合并查询结果 编辑:程序博客网 时间:2024/06/01 20:14

上次我讲了一下工厂方法模式,这次我来讲一下抽象工厂模式。

其实,我在工作当中基本没有用到过抽象工厂模式。我用的比较多的是工厂方法模式。我对抽象工厂模式的理解主要来自《设计模式》这本书。同时也搜索了一些网上的文章。

这里我就讲一下我自己的理解,如果讲的不好,或者根本就讲错了,请大家谅解。

 

抽象工厂模式 (Abstract Factory)

意图

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

 

结构图

从结构图来看,抽象工厂模式主要包括:工厂类和产品类。

我们可以从意图里面看到,抽象工厂模式的主要目的就是“提供一系列创建对象的接口”

我个人的理解是,抽象工厂模式是比工厂方法模式更加抽象的一种模式。当我们的程序里面有很多产品类时,我们就可以使用抽象工厂模式。

我们还是以上一篇文章里面的跑酷游戏(http://blog.csdn.net/zj510/article/details/8089191)的地形为例子。

之前我们用了工厂方法模式来创建地形,这次我们尝试用抽象工厂模式来创建。

我们这次增加一个新的产品类CWeather,这个类用来表示和地形有关系的天气效果。

先给出类图

从类图我们可以看到我们新增加了一个产品系列,天气类CWeather以及一个工厂类CFactory。

地形类以及地形类里面的背景,地面类和上一个文章一模一样,没有任何改动。

这里直接列出代码

class CComponent{public:virtual void LoadPicture() = 0;};class CTerrain{public:virtual void SetBackground(CComponent* bg){if (m_BackGround){delete m_BackGround;}m_BackGround = bg;}virtual void SetGround(CComponent* ground){if (m_Ground){delete m_Ground;}m_Ground = ground;}CTerrain(){m_BackGround = NULL;m_Ground = NULL;}virtual ~CTerrain(){if (m_BackGround){delete m_BackGround;m_BackGround = NULL;}if (m_Ground){delete m_Ground;m_Ground = NULL;}}protected:CComponent* m_BackGround;CComponent* m_Ground;};class CSnowBackground: public CComponent{public:virtual void LoadPicture(){std::cout<< "Load snow background picture \n"; }};class CForestBackground: public CComponent{public:virtual void LoadPicture(){std::cout<< "Load forest background picture \n"; }};class CSnowGround: public CComponent{public:virtual void LoadPicture(){std::cout<< "Load snow ground picture\n"; }};class CForestGround: public CComponent{public:virtual void LoadPicture(){std::cout<< "Load forest ground picture\n"; }};


接下来给出天气类CWeather,这个类里面只有一个函数Effect()

class CWeather{public:virtual void Effect() = 0;};class CSunWeather: public CWeather{public:virtual void Effect(){std::cout<< "Add sun effect \n";}};

这里我们只用一个子类CSunWeather来作为示例。这个类显示一个太阳效果。

 

然后看关键的工厂类:

class CFactory{public://生产一个Terrain对象virtual CTerrain* MakeTerrain(){return new CTerrain();}virtual CComponent* MakeBackground() = 0;virtual CComponent* MakeGround() = 0;//生产一个天气对象virtual CWeather* MakeWeather() = 0;};class CSnowSunFactory: public CFactory{public:virtual CComponent* MakeBackground(){return new CSnowBackground();}virtual CComponent* MakeGround(){return new CSnowGround();}virtual CWeather* MakeWeather(){return new CSunWeather();}static CSnowSunFactory& GetInst(){static CSnowSunFactory factory;return factory;}private:CSnowSunFactory(){}};


工厂类总共提供了4个接口,用来创建4个产品地形,背景,地面和天气。

其中地形类组合了背景和地面。

背景和地面是同一个层面的类。

天气是另外的一个类。

子工厂类CSnowSunFactory实现了这4个接口,然后生产雪地背景,雪地地面和太阳效果。由雪地背景和雪地地面来组成地形实例。

 

ok,现在再来看看CCreator类,这个类和上一个文章(工厂方法模式)里面的CCreator是一样的职责,这里我不过是改了成员函数Create的参数,主要就是用来传递一个factory实例进去,这样CCreator可以根据factory参数来创建相应的产品。看代码:

class CCreator{public:void Create(CFactory& factory, CTerrain** t, CWeather** w){CTerrain* terrain = factory.MakeTerrain();CComponent* bg = factory.MakeBackground();CComponent* ground = factory.MakeGround();bg->LoadPicture();ground->LoadPicture();terrain->SetBackground(bg);terrain->SetGround(ground);CWeather* weather = factory.MakeWeather();*t = terrain;*w = weather;}};


 ok, Create()函数返回2个对象,地形和天气。

其中地形部分和工厂方法模式中的一模一样,只是增加了天气对象。

(对于CTerrain对象,我们这里没有子类化,其实也是可以。)

客户端代码:

CSnowSunFactory& factory = CSnowSunFactory::GetInst();CCreator creator;CTerrain* terrain = NULL;CWeather* weather = NULL;creator.Create(factory, &terrain, &weather);weather->Effect();delete terrain;delete weather;

上面的代码例子创建了一个雪地地形配合下雪天气。

仔细的把抽象工厂模式和工厂方法模式相比,我们会发现,其实抽象工厂模式只是增加了一个工厂类CFactory,然后把CCreator里面的工厂方法移到了工厂类中。

然后通过传递一个参数,把CFactory的实例传到CCreator里面。

如果我们要增加森林配合下雨天气的场景,那么只需要增加一个新的工厂类,CForestRainFactory,然后实现相应代码(省略,实现跟CSnowSunFactory差不多)。

然后这样调用来创建森林,下雨效果。

CForestRainFactory& factory = CForestRainFactory::GetInst();CCreator creator;CTerrain* terrain = NULL;CWeather* weather = NULL;creator.Create(factory, &terrain, &weather);weather->Effect();delete terrain;delete weather;


通常我们把工厂类设计成单例的,因为一个工厂类通常只生产一个特定的产品系列。

 

那么抽象工厂模式有什么优点呢?

1. 首先,当我们的需求里面产品系列比较多或者复杂的时候,用一个专门的CFactory类来管理会比较好。这样实现了客户类和产品创建的分离;

2. 很容易更改一个工厂,从而更改所有的产品,因为一个工厂类实现了一个完整的产品体系;

3. 一个工厂类封装了一个完整的产品体系,那么也就是说一个应用一次只能使用同一个系列中的产品(由当前的工厂类决定)。

当然抽象工厂模式也有个很明显的缺点:

难以支持新的产品。 看上面的例子,CFactory里面只提供了4个接口。如果要支持新的产品,就得增加一个接口,这就会影响所有的子类。

这里有一个办法,通过参数来控制,比如我们增加一个接口:virtual void* MakeMagicProduct() = 0;

不同的工厂之类可以返回不同的产品。那么这里有个问题,对于调用者来说,怎么知道返回的是个什么类型呢?

比如:

void Create(CFactory& factory){      void* p = factory.MakeMagicProduct();}

那么怎么知道p是什么的?这个好像比较困难。如果用强行转换的话,感觉不太安全,或者不具有通用性。

我的理解是,真要这么做的话,就得小心一点。不然就直接改接口吧。

 

那么什么时候用工厂方法模式,什么时候用抽象工厂模式呢?这里引用书上对于什么时候使用抽象工厂模式的原话:

1. 一个系统要独立于它的产品的创建,组合和表示时;

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

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

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

上面的4点需要细细体会,其中第二点是最容易理解的,一个配置就是一个CFactory子类的实例。然后另外3点,我觉得可以简单理解为产品结构比较复杂并且有一定联系(比如地形是和天气联系在一起的)的时候。

 

回到跑酷例子,因为产品体系不复杂,而且我的那个小游戏里面,游戏场景是可以事先设定的,也就是说当玩家登录游戏主界面的时候,使用什么地形已经决定了。然后里面有一个CCreator的数据成员来专门创建各种地形,所以我的做法是每次登录主界面的时候就相应创建一个CCreator的子类的实例,然后赋值给CCreator数据成员。CCreator并不知道要创建什么背景,地面,这些是在CCreator的子类里面决定。所以这个地方用了工厂方法模式。

当然我们也可以用抽象工厂模式来实现。只是我觉得用工厂方法模式已经足够了。

这里再重复一次G4的一个建议:通常,设计以使用Factory Method开始,并且当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。


 

原创粉丝点击