您的位置:首页 > 产品设计 > UI/UE

设计模式-抽象工厂和builder模式

2016-06-09 04:05 525 查看

一、序言

系列博客文章都是研读Gof的Design

Patterns这本书的总结分享,书上的有些例子代码不是很全,这边依葫芦画瓢还原了一些代码,可供运行。目前,网络上很多分享设计模式内容的博客文章,都很经典,其中有个CSDN中的设计模式博客专栏也是研究的Gof的书籍,通俗易懂,让读者对设计模式一目了然。

自己在学习设计模式的过程中,有时候理解一个设计模式挺简单的,但是想要记住它,运用它,往往比较困难,所以系列文章的主要目的就是让设计模式不单单只是以一个软件模式存在于我们的认知中。更多的是想让读者包括我自己在心中织起一张设计模式的网络,更加体系化,在以后的面向对象的编程中,能够熟练地运用,在设计架构时能够信手拈来,多方面评估不同设计模式的优异性。一些基本的设计模式的定义及何时使用恰当这边不多赘述,这是CSDN博客专栏的网址,大家可以前往学习:


http://blog.csdn.net/wuzhekai1985/article/category/859763

另外系列博客文章以C++语言基础,以一个小的项目讲述同一类型的设计模式,学习的步骤基本是一步一步了解一个小项目时如何构成的,学习的过程中,设计模式的代入感并不会很强,当整个代码浏览完毕,设计模式才会体现出来,摆脱一种带着“就是这种设计模式”的套路去学习设计模式。java的话提供了一些API,我认为少了一点豁然开朗的感觉,用纯C++标准库开发,让人兴奋。

以上只是鄙人愚见,欢迎各路大神赐教。

二、抽象工厂模式

这边以Gof书上创建迷宫的系列例子来展开说明抽象工厂模式,创建型模式将都会用到这一套代码,代码后面会在Github上托管。
创建一个迷宫吧,定义一个迷宫基类MapSiteBase,迷宫的组成Room,Door,Wall等都会继承它:


class MapSiteBase
{
public:
MapSiteBase(){
cout<<"mapsite base."<<endl;
}
virtual ~MapSiteBase(){}//虚析构函数,保险

virtual void Enter() = 0;//为子类定义统一接口,派生类需要实现它
};


接下来是各个组成的定义,类当中的一些成员函数没有特别的去实现,大多数默认为空少数提供打印显示,主要是为了展示设计模式:

class Room : public MapSiteBase
{
public:
Room(int roomNo){}
~Room(){}
virtual void Enter(){}
private:
int _roomNo;

};
class Door : public MapSiteBase
{
public:
Door(Room* = 0,Room* = 0){}//一面墙紧挨着两个房间
virtual void Enter(){}
private:
Room* _romm1;
Room* _romm2;
};
class Wall : public MapSiteBase
{
public:
Wall(){}
virtual void Enter(){}
};


以上是三个组件的简单类定义,组件构成迷宫,简单说wall & door 构成 room,一些列room构成迷宫,需要定义一个迷宫类:

class Maze
{
public:
Maze(){}
void addRoom(Room*){}//往迷宫中添加Room
Room* RoomNo(int) const{}
};


迷宫的基本组件都定义完毕,接下里就是构建过程,定义一个迷宫构建类,简单说是时候写一个迷宫游戏了:

class MazeGame
{
public:
MazeGame(){}
virtual ~MazeGame(){}
virtual Maze* createMaze(MazeFactory &factory);//创建迷宫的方法
virtual Maze* createMaze(MazeBuilder &builder);//可忽略,builder中用到

};


由上面可以看到这个时候去实现Maze中的createMaze这个成员函数就可以构造迷宫了,仔细想想,最简单暴力的就是直接在这个成员函数中new Room、Door、Wall,然后一一组合制定之间的关系,构成一个复杂的迷宫。但是万一需要改变一下迷宫的规则或者添加一些特性比如施有魔法的迷宫等,就要重新对这个成员函数进行编码了。疑问先晾在这,后面自会明朗。

为了让createMaze变得更加灵活,减少编码,定义一个可扩张的创建迷宫游戏的类:

class MazeFactory
{
public:
MazeFactory(){}
virtual ~MazeFactory(){}
virtual Maze* makeMaze() const//成员函数都默认实现创建普通的迷宫,定义为virtual为了派生类实现他们感兴趣的方法
{
return new Maze();//立即返回
}
virtual Wall* makeWall() const
{
return new Wall();
}
virtual Room* makeRoom(int num) const
{
return new Room(num);
}
virtual Door* makeDoor(Room* r1,Room* r2) const
{
return new Door(r1,r2);
}

};


接下来看一下createMaze的简单实现,传入的参数是上述类的对象,通过这个参数调用对应的成员函数,实现创建迷宫,咋一看实现比之前明朗一点。创建一个简单的两个Room,一个Door的迷宫。

Maze* MazeGame::createMaze(MazeFactory &factory)
{
Maze *maze = factory.makeMaze();
Room *r1 = factory.makeRoom(1);
Room *r2 = factory.makeRoom(2);
Door *door = factory.makeDoor(r1,r2);

maze->addRoom(r1);
maze->addRoom(r2);

/*wall init*/

}


上面创建了这么多类,也许有点混乱了,按道理来说作者应该给出一张UML图来表明一下这些类的关系,但我不能给出,希望有心人自己可以把它们画出来,往往是提供了UML有些读者才不会认真的去构思其中的联系。现在开始讲述抽象工厂这个设计模式了,我们先看一下上述一大堆的定义,该怎么用了,简单TEST:

Maze *maze;//声明一个迷宫
MazeFactory fac ;//声明一个创建迷宫游戏的工厂!!

MazeGame mgame;//声明一个迷宫游戏
maze = mgame.createMaze(fac);//构建迷宫游戏,得到迷宫的handle赋值给maze。


这里有读过其他博客的读者或许会怀疑,抽象工厂模式应该是有一个抽象工厂定义接口,一个实际工厂实现这些接口。是的,然而MazeFactory 充当了两个的角色,因为MazeFactory 有缺省的实现,所以既可以是抽象工厂也可以是实际工厂。下面继续说明一个例子,也许会让读者对这个模式的优点有更深的认识。

是的,如果要创建一个带有魔法的Room了,需要spell的Door呢,是否又要重新编码了?来看一下抽象工厂模式是怎么实现的吧,下面是一系列类的定义:

class Spell//咒语类,给迷宫的一些组件增加点color
{
public:
Spell(){}
void show(){cout<<"this room is magic!"<<endl;}
};
//定义一个魔法Room,继承父类Room
class EnchantedRoom : public Room
{
public:
EnchantedRoom(int num,Spell *spell):Room(num){spell->show();}
//构造函数的参数包括Room的number,还有带spell的属性,:Room(num)这个加入主要是为了基类的构造函数参数个数类型不一致,派生类的参数与基类构造函数的参数不一致时,需要在派生类中指定基类的参数。
};
//定义一个Door,需要spell才能open的Door
class DoorNeedingSpell : public Door
{
public:
DoorNeedingSpell(Room* = 0,Room* = 0){show();}
void show(){cout<<"this door need spell!"<<endl;}

};
//you should be careful!定义一个工厂继承MazeFactory,具体重载了makeRoom和makeDoor这两个创建迷宫组件的方法。
class EnchantedMazeFactory : public MazeFactory
{
public:
EnchantedMazeFactory(){}
Spell* CastSpell() const{return new Spell();}

virtual Room* makeRoom(int num) const
{
return new EnchantedRoom(num,CastSpell());
}
virtual Door* makeDoor(Room* r1,Room* r2) const
{
return new DoorNeedingSpell(r1,r2);
}

};


当然如果感兴趣还可以加入一个会爆炸的Wall进去,书中有稍微说明了以下,这边就不花篇幅描述了,原理差不多。意在阐释这下我要是修改一些迷宫的特性不需要去修改createMaze这个方法也不需要去动基类Room、Wall的定义。一切靠派生出来的类即能解决,下面看一下上面例子的TEST:

Maze *maze;
MazeFactory *fac = new EnchantedMazeFactory();

MazeGame mgame;
maze = mgame.createMaze(*fac);


领悟到一个设计模式的好处优点,才会激起你往深处理解这个设计模式的欲望。抽象工厂模式帮助我们控制一个应用创建的对象的类。它封装了创建产品(迷宫游戏)的过程,将客户与类的实现分离。到此你可以把迷宫看做成一个产品,产品多种多样(迷宫的特性千变万化,这里不包括数量),但基本是一套工厂能搞定,不妨回到代码的开始再重新阅读一遍,带着自己对设计模式的理解重新理解这些类的定义和联系。当然要创建复杂多个ROOM,结构五花八样的迷宫,那么抽象工厂模式显得心有余而力不足了。

三、Builer设计模式

上面讲到创建一个复杂的,有不同结构的迷宫有些麻烦了,这个时候builder模式上场了,它旨在将一个复杂对象(迷宫可以看成一个复杂对象)的创建于它的表示分离使得同样的构建过程可以创建不同的表示。好了,这里不多云该模式的其他吊炸天的定义了。。。。直接切入主题,让我们继续码代码,延续上面创建迷宫的例子。

上面注意到createMaze方法有两个,当中有一个传入的参数是MazeBuilder对象,从地位上来讲你也许会联想到,这个类是一个跟抽象工厂基类同等级别的存在:

virtual Maze* createMaze(MazeBuilder &builder);


接下来先定义一个MazeBuilder 类,看类名就知道这是该设计模式的core了:

class MazeBuilder
{
public:

virtual ~MazeBuilder(){}
//跟抽象工厂模式类似,定义一系列创建迷宫的接口,缺省实现为空
virtual void buildMaze(){}
virtual void buildRoom(int num){}
virtual void buildDoor(int fromR,int toR){}
virtual void buildWall(){}

virtual Maze* getMaze(){return 0;}//get 方法获得迷宫的handle,需要构建到最后一步才能返回
protected:
MazeBuilder(){}
};


当然这里也可将MazeBuilder实现成同MazeFactory一样有缺省实现的类,也就是每个build方法中有对应的简单实现,这里就不多赘述了。下面看一下迷宫游戏是如何被创建的:

Maze* MazeGame::createMaze(MazeBuilder &builder)
{
builder.buildMaze();

builder.buildRoom(1);
builder.buildRoom(2);

builder.buildDoor(1,2);

return builder.getMaze();
}


In builder pattern,MazeGame class is like a Director class,Director class 是一个使用Builder接口的类。像其他创建型模式,builder设计模式封装了对象是如何创建的过程,通过MazeBuilder这个类所定义的接口进一步封装。这样一类我们可以重载MazeBuilder中的方法实现不同种类的迷宫,或者重载createMaze来实现不同结构的迷宫。比如创建一个标准迷宫:

class StandarMazeBuilder : public MazeBuilder
{
public:
StandarMazeBuilder(){_currentMaze = 0;}
//只重载自己感兴趣的方法
void buildMaze(){_currentMaze = new Maze();}
void buildRoom(int num){
Room *r1 = new Room(num);
_currentMaze->addRoom(r1);
}
void buildDoor(int fromR,int toR){
Room *r1 = _currentMaze->RoomNo(fromR);
Room *r2 = _currentMaze->RoomNo(toR);

Door *d = new Door(r1,r2);
DoorSpell();
}
//virtual void buildWall(){}

Maze* getMaze(){return _currentMaze;}//灵魂实现,通过此接口向客户提供迷宫产品的handle
private:
Maze *_currentMaze;
void DoorSpell(){cout<<"this door needs spell!"<<endl;}

};


MazeBuiler类自己并不创建迷宫,他只是提供一系列公共接口,它的派生类才做实际的工作,下面是Builder的TEST:

Maze *maze;
MazeBuilder *mz = new StandarMazeBuilder();

MazeGame mgame;
maze = mgame.createMaze(*mz);
maze = mz->getMaze();


至此,会发现该模式与抽象工厂模式真像,都可以创建复杂的对象,主要区别是Builder模式注重一步一步构造一个复杂的模式,二抽象工厂模式着重于多个系列的产品对象。Builder在最后一步返回产品,而Factory是立即返回的。

这边也许对Builder模式的应用还不够深入,我特地生搬硬套到了linux socket上写了一个不伦不类TCP/IP、UDP的Builder设计模式应用的例子,不过效果还不错:

下面是Builder类,由于类别不像迷宫那么多种多样,于是缺省实现了当中接口的功能,这下都是有实际应用的功能,可以更容易看了

class SocketBuilder
{

public:

///定义sockfd
int _server_sockfd;
///定义sockaddr_in
struct sockaddr_in _server_sockaddr;
int _port;
int _client_sockfd;
char _ip_addr[20];

public:
SocketBuilder(int port):_port(port){

}
SocketBuilder(int port,char ip_addr[20]):_port(port){
strcpy(_ip_addr,ip_addr);
}
~SocketBuilder(){
_close();
}
//依葫芦画瓢,通通定义成virtual 类型,虽然可能没有派生类,下面一系列接口,相信了解TCP/IP、UDP通信的读者可以知道,不同的协议过程也不一样。
virtual  void _initCommon()
{
_server_sockaddr.sin_family = AF_INET;
_server_sockaddr.sin_port = htons(_port);
_server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

}
virtual void _initClient()
{
_server_sockaddr.sin_family = AF_INET;
_server_sockaddr.sin_port = htons(_port);
if( inet_pton(AF_INET, _ip_addr, &_server_sockaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n",_ip_addr);
exit(0);
}

}
virtual void build_socket()
{
_server_sockfd = socket(AF_INET,SOCK_STREAM, 0);

}

virtual void build_bind()
{
///bind,成功返回0,出错返回-1
if(bind(_server_sockfd,(struct sockaddr *)&_server_sockaddr,sizeof(_server_sockaddr))==-1)
{
perror("bind");
exit(1);
}
}
virtual void build_listen()
{
///listen,成功返回0,出错返回-1
if(listen(_server_sockfd,QUEUE) == -1)
{
perror("listen");
exit(1);
}
}

virtual void build_accept()
{
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);

///成功返回非负描述字,出错返回-1
_client_sockfd = accept(_server_sockfd, (struct sockaddr*)&client_addr, &length);
if(_client_sockfd < 0)
{
perror("connect");
exit(1);
}
}
virtual void build_connect()
{
int err=connect(_server_sockfd,(struct sockaddr *)&_server_sockaddr,sizeof(_server_sockaddr));
if(err==-1)
{
printf("connect error\n");
//return -1;
}
}

virtual  void _close()
{
close(_client_sockfd);
close(_server_sockfd);
}

};


接下来为了编码简单点,我把Director实现成能构造UDP 、TCP的的类,类的内部实现了简单的判断:

class SocketDirector
{
SocketBuilder *_mBuilder = NULL;

public:
SocketDirector(SocketBuilder *mBuilder){
_mBuilder = mBuilder;
}
void construct(SERVER_OR_CLIENT_T soc){
switch(soc)
{
case TCP_SERVER:
_mBuilder->_initCommon();
_mBuilder->build_socket();
_mBuilder->build_bind();
_mBuilder->build_listen();
//_mBuilder->build_accept();

break;
case UDP_SERVER:
_mBuilder->_initCommon();
_mBuilder->build_socket();
_mBuilder->build_bind();

break;
case TCP_CLIENT:
_mBuilder->_initClient();
_mBuilder->build_socket();
_mBuilder->build_connect();

break;
case UDP_CLIENT:
_mBuilder->_initClient();
_mBuilder->build_socket();
break;
default:
printf("please input correct protocols.\n");
break;
}

}
};


看了以上实现,你会发现只要实现了这两个类,那么无论是TCP Server、Client还是UDP Server、Client都只需要调用这两个类就可以实现复杂的部署了。接下里就是测试TEST,比如写一个TCP server端的代码,你可能只要:

SocketBuilder *sBuilder = new SocketBuilder(PORT);
SocketDirector *sDirector = new SocketDirector(sBuilder);

sDirector->construct(TCP_SERVER);

while(1)
{
sBuilder->build_accept();

while(1)
{
printf("waiting for client...\n");
n=read(sBuilder->_client_sockfd,recvline,1024);
if(n==-1)
{
printf("recv error\n");
}
recvline
='\0';

printf("recv data is:%s\n",recvline);

printf("Input your words:");
scanf("%s",sendline);
write(sBuilder->_client_sockfd,sendline,strlen(sendline));
}
}


对应client代码:

SocketBuilder *sBuilder = new SocketBuilder(PORT,ip);
SocketDirector *sDirector = new SocketDirector(sBuilder);

sDirector->construct(TCP_CLIENT);

while(1)
{
printf("Input your words:");
scanf("%s",sendline);

write(sBuilder->_server_sockfd,sendline,strlen(sendline));

printf("waiting for server...\n");

n=read(sBuilder->_server_sockfd,recvline,100);
recvline
='\0';

printf("recv data is:%s\n",recvline);
}

sBuilder->_close();


至此,也许有些读者对上面linux的例子不太清楚,那能怎么办?只要你深入理解设计模式,何处不在了。扶我起来,我还能继续码代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: