0%

设计模式学习总结1

说明:这是一个个人学习的入门性总结,而且其中多是一些个人的理解,没有什么抄书的内容。所以如果有问题请指出。

常见的设计模式分为3类:创建型,结构型,行为型。分别用来完成对象的:创建、组合、交互和职责分配。

创建型模式

抽象工厂

这个模式用来创建一个“系列”产品,简化创建过程,并让客户类不再操心具体的创建细节。比如假设现在你在玩一个游戏,游戏主人公可以用手头的材料合成成套的盔甲。盔甲的材质有皮,铁,黄金,钻石。在不同的环境下,你希望使用不同的盔甲套装(比如一般情况下穿皮甲方便行动,外出穿铁甲加强防护,打怪的时候穿金套装)。显然每次根据不同的情况"new"一堆装备是很麻烦的。为了让不再每次都操心具体的创建过程,并且保证每次创建的装备的材质都一样,可以声明一个抽象工厂类,在其中声明一些抽象方法用来创建一套完整的装备。到具体要创建东西的时候,调用实现了这个抽象类中抽象方法的具体类就可以了。抽象工厂的每个具体类负责创建具体某种材质的套装。显然为了让套装中的每种装备(比如头盔,外套)有不同的材质,每种装备都应该声明为抽象类,这样不同材质的装备就可以从基类派生出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<iostream>
using namespace std;

class Helmet{
public:
virtual void use()=0;
};

class Coat{
public:
virtual void use()=0;
};

class GoldHelmet: public Helmet{
public:
void use(){ cout<<"use GoldHelmet"<<endl; }
};

class GoldCoat: public Coat{
public:
void use(){ cout<<"use GoldCoat"<<endl; }
};

class IronHelmet: public Helmet{
public:
void use(){ cout<<"use IronHelmet"<<endl; }
};

class IronCoat: public Coat{
public:
void use(){ cout<<"use IronCoat"<<endl; }
};

class ArmorFactory{
public:
virtual Helmet* MakeHelmet()=0;
virtual Coat* MakeCoat()=0;
};

class GoldArmorFactory : public ArmorFactory{
public:
virtual Helmet* MakeHelmet(){return new GoldHelmet;}
virtual Coat* MakeCoat(){return new GoldCoat;}

};

class IronArmorFactory : public ArmorFactory{
public:
virtual Helmet* MakeHelmet(){return new IronHelmet;}
virtual Coat* MakeCoat(){return new IronCoat;}
};

int main(){
Helmet *hforest, *hmonster;
Coat *cforest, *cmonster;
//当你在树林里用铁甲
ArmorFactory *ia = new IronArmorFactory;
hforest = ia->MakeHelmet();
cforest = ia->MakeCoat();

hforest->use();
cforest->use();

//当你打怪用金甲
ArmorFactory *ia2 = new GoldArmorFactory;
hmonster = ia2->MakeHelmet();
cmonster = ia2->MakeCoat();

hmonster->use();
cmonster->use();

return 0;
}

以上的例子有两个工厂,一个负责创建金装备,另一个负责创建铁装备。另外从代码中还可以看出,工厂的指针可以作为一个参数传递,这样就可以在程序的其它地方使用工厂了。

另外也可以把工厂作为一个单例,因为一般来说程序中没有必要反复创建工厂类的实例。

最后抽象工厂有个缺点:如果在产品系列中增加新的类型(比如套装中增加了斧头),那么抽象工厂的基类和各个子类都要被修改。

单例模式

有时候需要全局只有一个对象实例,比如刚才的工厂,当需要某种材质的套装时只需要调用唯一的工厂实例创建套装就行了。

解决方法是:构造函数不要声明为public。并且在类声明内定义private的该类型的静态指针变量。这样就解决了唯一性问题。为了让外部访问唯一的对象实例,还要提供一个public方法,返回唯一实例的指针。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton{
public:
static Singleton* getInstance(){
if(_instance==NULL) _instance = new Singleton;
return _instance;
}
protected:
Singleton();
private:
static Singleton* _instance;
};

Singleton* Singleton::_instance = NULL;
以上属于“懒汉式”单例。它在首次需要用到实例时才创建实例,而"饿汉式"则在程序开始就初始化实例。如果初始化依赖某些程序运行时的信息,“懒汉式”单例就派上了用场。

工厂方法模式

首先要介绍一下简单工厂模式。 #### 简单工厂 假设现在某个程序需要建立一个链接访问系统的某个资源。我们假设这个链接类的名子叫Connection。如果系统为该资源提供了多种访问方式,那么这个链接类也对应有很多子类(子类和访问方式对应)。如果这个资源的每种访问方式都涉及很多其它的类,操作,那么最好把这些东西都封装起来,让客户不关心Connection如何创建的。这样客户只要指定访问方式,然后使用Connection对象访问资源就行了。

比如:

1
Connection con = ConFactory().createConnection(way);
这样就把方式当做参数传入,根据不同参数返回不同的Connection子类。ConFactory代码可能是这个样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ConFactory{
public:
//。。。。
Connection* createConnection(int way){
switch(way){
case 1:
ConnectionA *con1 = new ConnectionA;
//一些准备工作
return con1;
case 2:
ConnectionB *con2 = new ConnectionB;
//一些准备工作
return con2;
//。。。
}
}
};

这个设计模式虽然解决了客户创建对象的问题,使得客户不再需要关心对象创建的细节,但是很明显,如果增加了Connection类型,那么类内switch部分的代码又要修改。这违背了“开放-封闭原则”。

工厂方法

简单工厂模式违背“开放-封闭原则”主要是因为内部的逻辑判断。为了克服这个问题,逻辑判断可以放在客户代码里。以前为了创建Connection对象要做很多准备工作,现在代码改了,怎么办?这里采取的方法是:把工厂类弄成抽象的,然后每种Connection子类对应一个抽象工厂的具体实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ConFactory{
public:
virtual Connection* createConnection()=0;
};

class ConFactoryA{
public:
virtual Connection* createConnection(){
ConnectionA *con1 = new ConnectionA;
//一些其它工作。。。
return con1;
}
};

class ConFactoryB{//。。。。。。

需要用某个Connection类的时候可以这样:

1
2
3
4
5
6
7
Connection con;
switch(way){
case 1:
con = ConFactoryA().createConnection();
break;
//。。。
}
或者如果事先知道了需要何种Connection,也可以直接用相应的工厂创建对象。

从上面也可以看出,抽象工厂就是工厂方法中每种工厂类可以创建一批对象的情形。

生成器模式

如果需要构建一个复杂的对象,甚至这个对象还需要进行一定的“组装”(各个部件之间有些联系),那么如果把生成和装配的代码分离出来会比较好。比如:现在有一个房屋生成的程序,它建造房子的流程是固定的几项(先造地基,然后是盖一定的层数,安装房顶,内部装修)。如果希望给房子选一些不同风格的部件,可以把这些盖房流程抽象成一个Builder接口。然后用不同的ConcreteBuilder类实现这些接口,这样每种具体的Builder的实现就能够盖出一个不同的房子。

Builder的组装受一个Director类的控制。Director调用Builder中的接口方法实现构造对象。显然Director不关心究竟怎么构建对象的。只要增加一个Builder的实现,不用修改代码,就可以让Director造一个新类型的房子。Director调用Builder里方法的顺序也不是固定的,可以根据情况进行一些动态的调整(比如GOF书里RTF文档阅读器的例子)。

下面的代码制造一个木头房子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

class House{};
class WoodHouse : public House{};

class HouseBuilder{
public:
virtual void buildBase()=0;
virtual void buildWalls()=0;
virtual void buildRoof()=0;
virtual void buildFurniture()=0;

virtual House* getHouse()=0;
};

class WoodHouseBuilder{
WoodHouse* whouse;
public:
virtual void buildBase(){ //..... }
virtual void buildWalls(){ //..... }
virtual void buildRoof(){ //..... }
virtual void buildFurniture(){ //..... }

virtual House* getHouse(){return whouse;}
};

House* HouseDirector(const HouseBuilder& builder){
builder.buildBase();
builder.buildWalls();
builder.buildRoof();
builder.buildFurniture();
return builder.getHouse();
}

int main(){
WoodHouseBuilder b;
House* house = HouseDirector(b);
return 0;
}

如果需要别的房子,只要相应地增加HouseBuilder和House的子类就可以。然后调用HouseDirector方法完成组装。

原型模式

这是本文介绍的最后一个设计模式。

这个设计模式主要通过“拷贝”实现对象的构造。比如,现在有个画板程序。其中有许多预置图形(继承自同一基类Shape)。为了实现快速生成一个图形对象,可以采用实现构造一个对象实例,然后复制它的方法。这样:第一,如果需要增加新的图形,那么只要继承图形基类Shape,其它代码不变即可。第二,因为只是复制对象,所以创建图形的代价会减小。为了复制对象,可以为要复制的类定义clone方法。如果在创建复制对象的时候想对复制的对象进行一些修改,可以定义Initialize方法。

附一个GOF书上迷宫生成器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class MazePrototypeFactory(Maze*, Wall*, Room*, Door*){
Maze* _prototypeMaze;
Room* _prototypeRoom;
Wall* _prototypeWall;
Door* _prototypeDoor;
public:
MazePrototypeFactory(Maze*, Wall*, Room*, Door*);

virtual Maze* MakeMaze() const;
virtual Room* MakeRoom(int) const;
virtual Wall* MakeWall() const;
virtual Door* MakeDoor(Room*, Room*) const;
};

MazePrototypeFactory::MazePrototypeFactory(
Maze* m, Wall* w, Room* r, Door* d)
{
_prototypeMaze = m;
_prototypeRoom = r;
_prototypeWall = w;
_prototypeDoor = d;
}

Wall* MazePrototypeFactory::MakeWall()const{
return _prototypeWall->clone();
}

Door* MazePrototypeFactory::MakeDoor(Room* r1, Room* r2)const{
Door* door = _prototypeDoor->clone();
door->Init(r1, r2);
return door;
}

MazeGame game;
MazePrototypeFactory SimpleMazeFactory(
new Maze, new Wall, new Room, new Door
);

Maze* maze = game. createMaze(SimpleMazeFactory);