上次我讲了一下工厂方法模式,这次我来讲一下抽象工厂模式。
其实,我在工作当中基本没有用到过抽象工厂模式。我用的比较多的是工厂方法模式。我对抽象工厂模式的理解主要来自《设计模式》这本书。同时也搜索了一些网上的文章。
这里我就讲一下我自己的理解,如果讲的不好,或者根本就讲错了,请大家谅解。
抽象工厂模式 (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开始,并且当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。