小弟我最近重新学习设计模式。
经典就是经典,这本书我已经看了至少两遍了,这次重新再看一次,发现又体会到不同的东西了。
身边很多朋友都在写博客,那么我也凑凑热闹,一来可以加深自己的学习,二来也可以给朋友们参考参考。
限于本人能力有限,如果写的不好的地方请多多包涵,如果有什么理解错误的地方,欢迎指出。
好了,废话少说,切入正题。先从创建型模式开始吧。
我最熟悉的语言是C++,所以所有的例子都由C++来实现。
根据G4的说法,创建型模式包括: 工厂模式,抽象工厂模式,生成器模式,原型模式, 单件模式。先从工厂方法模式开始。
工厂方法模式(Factory Method)
意图:
定义一个用于创建对象的接口,让子类来决定实例化哪一个类。 Factory Method使一个类的实例化延迟到其子类。
结构图:
工厂模式是本人使用比较多的一种模式。根据G4的说法,我们可以首先考虑工厂模式,如果工厂模式不能满足需求,再考虑其他模式。
这个模式本身比较简单,主要分两部分:
1. 工厂方法类 (Creator)
2. 产品类 (Product)
我觉得这个模式的关键在于“使一个类的实例化延迟到其子类”。也就是说很多时候可能一个类并不知道它所需要创建的对象的类。
看上面的结构图,在class Creator里面有两种方法:
1. FactoryMethod
2. AnOperation
FactoryMethod()就是我们工厂方法模式提到的“工厂方法”, AnOperation是另外一个方法,这个方法可以是虚函数,也可以是非虚函数,个人比较喜欢虚函数,因为比较灵活。AnOperation需要用到FactoryMethod()创建出来的实例。而这些FactoryMethod()应该由Creator的子类(ConcreteCreator)来实现,Creator只是提供一个接口说明。也就是说Creator并不知道FacotryMethod()会创建什么类(ConcreteCreator知道)。我觉得这个是工厂方法模式的要点,其实也就是G4讲的“意图”。
讲设计模式最好的方式就是结合例子,我这里也来讲一个我自己用过的一个例子。
小弟我以前写过一个小游戏,一个跑酷游戏。这个游戏里面有个地形模块,地形模块有雪地地形,森林地形等。
那么如何来创建这些地形呢,我就用了个工厂方法模式。
我这里抽象了几个类
1. CTerrain,这是个表示地形的类,地形由2部分组成:背景和地面。
2. CComponent,这是个基类,也就是工厂方法模式中的“产品类”,
3. CCreator,就是用来创建地形(CTerrain)的一个类。
先来看看类图:
1. CCreator 是一个包括工厂方法的类
CCreator里面有个方法叫做Create(), Create() 返回一个地形类(CTerrain),MakeBackground和MakeGround是虚函数,也就是工厂方法模式中的工厂方法(大多数情况下,这2个方法有子类来实现,当然基类也可以提供一个缺省实现,但是我觉得没什么必要)。函数Create()会调用MakeBackground和MakeGround来创建一个地形实例。
CSnowCreator,CForestCreator是CCreator的子类,这两个类实现了工厂方法MakeBackground和MakeGround。
2. CComponent就是产品类
我们可以看到它有几个子类。
那么我是怎么实现这几个类的呢,下面给出代码:
首先是产品类的基类,CComponent
class CComponent
{
public:
virtual void LoadPicture() = 0;
};
很简单,里面就一个纯虚函数LoadPicture().
然后是它的四个子类
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";
}
};
这4个类也很简单,就是实现了虚函数LoadPicture(),各自load自己的图片。
再一个类就是CTerrain, 这个类由2部分组成,一个背景类实例,一个地面类实例。
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;
};
好了,接下来我们看工厂方法模式的关键,CCreator类,先看下面的代码
class CCreator
{
public:
CTerrain* Create()
{
CTerrain* terrain = MakeTerrain();
CComponent* bg = MakeBackground();
CComponent* ground = MakeGround();
bg->LoadPicture();
ground->LoadPicture();
terrain->SetBackground(bg);
terrain->SetGround(ground);
return terrain;
}
protected:
virtual CTerrain* MakeTerrain()
{
return new CTerrain();
}
virtual CComponent* MakeBackground() = 0;
virtual CComponent* MakeGround() = 0;
};
从代码里面可以看到,这里有3个虚函数,这些就是工厂方法模式的工厂方法。
首先, MakeTerrain返回一个CTerrain实例。有些人觉得这个可以是虚函数,也可以是非虚函数。他们的区别也就是MakeTerrain()是否需要被子类继承来创建不同的Terrain,比如CTerrain的子类,我个人觉得这个没有绝对的。应该视具体情况而言。不过我的习惯是把它搞成虚的。
另外还有两个虚函数MakeBackground()和MakeGround(). 这2个函数一定是虚的(一般都是纯虚函数),因为需要子类来实现。(对于MakeTerrain,我也见过有些人不需要这个函数,而直接在Create()里面写死,返回一个Terrain实例,比如http://www.codeproject.com/Articles/35789/Understanding-Factory-Method-and-Abstract-Factory, 我觉得这个没什么关系,看个人需要了)
最后一个是public函数Create(),这个函数会调用MakeTerrain,MakeBackground,MakeGround来创建一个CTerrain实例,并且返回。也就是说这个函数并不知道MakeBackground和MakeGround会创建什么类型的产品。因为这些产品是在CCreator的子类里面才实现的。
下面给出CCreator两个子类的代码
class CSnowCreator: public CCreator
{
public:
virtual CComponent* MakeBackground()
{
return new CSnowBackground();
}
virtual CComponent* MakeGround()
{
return new CSnowGround();
}
};
class CForestCreator: public CCreator
{
public:
virtual CComponent* MakeBackground()
{
return new CForestBackground();
}
virtual CComponent* MakeGround()
{
return new CForestGround();
}
};
客户端怎么调用呢,看:
//创建一个雪地地形
CSnowCreator snowCreator;
CTerrain* snowTerrain = snowCreator.Create();//这里就得到了一个CTerrain的实例,这是个雪地地形
delete snowTerrain;
//创建一个森林地形
CForestCreator forestCreator;
CTerrain* forestTerrain = forestCreator.Create();//得到森林地形
delete forestTerrain;
好了,这个就是工厂方法模式的一个概要了。那么我们搞了半天,究竟用这个模式给我们带来什么好处呢。我觉得主要优点是:
客户端无需和具体的产品打交道,比方说客户端根本无需知道CSnowBackground类。也就是说我们通过工厂方法模式使得客户端和具体的产品类解耦。这个符合设计模式的一个基本要求:高内聚低耦合。客户端需要打交道的是创建产品的类CCreator,比如说我们现在需要增加一个新的地形沙漠,那么我们增加一个新的CCreator子类CDesertCreator和新的产品类CDesertBackground,CDesertGround。然后客户端创建一个CDesertCreator实例就行了,无需知道CDesertBackground和CDesertGround的存在。这样就比较容易扩展。
那么他有什么缺点呢:
每增加一个新的产品,可能就得增加一个CCreator子类。对于这个问题,我们可以用template来实现一个CCreator类,比如:
template<class BACKGROUND, class GROUND>
class CSuperCreator: public CCreator
{
public:
virtual CComponent* MakeBackground()
{
return new BACKGROUND();
}
virtual CComponent* MakeGround()
{
return new GROUND();
}
};
怎么调用呢,看下面:
//使用模板类
CSuperCreator<CSnowBackground, CSnowGround> creator;
CTerrain* snowTerrain = creator.Create();
delete snowTerrain;
如果要使用沙漠地形呢,可以这么搞
//使用模板类
CSuperCreator<CDesertBackground, CDesertGround> creator;
CTerrain* desertTerrain = creator.Create();
delete snowTerrain;
这样好像避免了增加新的CCreator子类,因为用template类来代替了。其实我个人是不太用template的,当然这个是个人喜好,怎么都行。
设计模式只是一些牛人总结出来的一些经验,没有绝对的,你觉得用的上就用,用不上也没必要死搬硬套,这样反而把事情搞复杂化。如果我们能从某个设计模式里面得到一些好处,那么就用。工厂方法模式还是挺实用的,简单易懂。我自己的习惯是首先考虑工厂方法模式,如果工厂方法模式不能满足我的要求,再考虑其他的,比如抽象工厂模式。抽象工厂模式比较接近于工厂方法模式,我们下一次就讲讲抽象工厂模式。另外工厂方法模式有点像行为模式里面的模板模式(template pattern),模板模式是把算法的一些步骤放到基类实现,而一些关键的算法或者比较会变动的算法让子类来实现。从某种意义上讲,也是把一些方法延迟到子类来实现。