现在的位置: 首页 > 综合 > 正文

乱砍设计模式之四 — COMPOSITE与BUILDER模式

2013年10月04日 ⁄ 综合 ⁄ 共 7028字 ⁄ 字号 评论关闭
COMPOSITE与BUILDER模式 —— 忠义堂石碣受天文 梁山泊英雄排座次

junguo

     Composite模式的中文名字是组合模式,该模式的目的是使单个对象和它的对象组合(一般是数组或者链表的结构)拥有统一的操作方式,这样可以简化客户的使用。我们还是通过具体的例子来理解该模式。还是先来一段例子背景介绍:
     话说,宋江带人攻陷东平东昌两郡,收降了双枪将董平和没羽箭张清,而后皇甫端来归。梁山聚集了武将,书生,医生以至兽医等各色人等,成为了一个成熟的社区。着点人数正好一百单八人,是个吉利数字,为此宋江组织了浩大的祭天仪式。在祭天仪式进行期间,发现了埋于地下的石碣,石碣上排定了三十六天罡,七十二地煞的座次。次序以定,梁山也开始走上正规,开始划分各位好汉的职责。依据各人特长,分别任职于马军,步军,军师等角色。有了规矩,才成方圆,自此梁山告别了单挑的时代,作战有了一些固定的组合。如李逵出战,必有项充,李衮护在身边;这位头脑简单的杀人魔王的典型特点是攻强守弱,而且不着盔甲,如若无人保护估摸早死了。还有另外一些组合:如矮脚虎王英和一丈青扈三娘这样的夫妻组合(扈三娘嫁给了王英,你能想通吗?我想不通);还有解宝解珍这样的兄弟组合等。
     来考虑一下,如何实现这样的功能,武将可以单独出击,也可以成群的出击?该如何去实现这样的功能呢?还是一步一步来,先看类图:

    这是我们建立的武将类,该类和我们第一讲《赵子龙单骑救主》中建立的武将类相同(为了突出中心功能,简化了功能)。考虑一下在这种情况下,我们如果想让多个武将一起出击,该如何实现?最单纯的做法就是在调用该类的地方生成一个数组,然后客户端通过该数组来调用各个对象的操作。大概代码如下:

General general[10];  
forint  i = 0; i<10; i++  )  
    general[i].Assault();

    客户端也可以将这些操作封装在不同的函数中,但如果武将类填加新的函数的时候,客户端也需要相应的增加此类德函数。这是典型的封装不好的例子,客户端需要增加特殊的函数来支持对象。我自己的工作中就遇到了这样的问题,类没有封装好,针对于这些类的操作,你不得不填加一些函数,接着就可以发现在不同的文件里有很多功能相似的函数(一个类往往在多处用到,而这些往往是由不同的人写的,这样很容易造成重复代码)。整个程序被改得乱码七糟的,改这样的东西真是头痛万分,更加上你如果要用到这些外部的函数的时候,都不知道用那个。那如何来解决该问题呢?找到组合模式,就找到了解决之道。先来看看组合模式的类图描述:

    Component是为Leaf和Composite抽象出来的共同接口,可以通过它来统一操作单个对象或者组合对象。
    Leaf是我们具体要用到的类,它用来完成具体的功能。Composite是我们用来组合对象的类,并实现对子对象的管理;它通过Add,Remove和GetChild来实现对子对象的管理。Composite中会含有一个Component型的对象列表childers,它也会重载Leaf中所需要的函数,然后通过遍历执行所有childer的函数。该childer也可以是Composite型的,这样就可以有一个递归的结构。
    我们用Composite模式来重新设计我们的类图,如下:

     理解模式最有效的方式,还是看具体的代码:

#include
#include 
#include 
#include 
using namespace std;

//抽象部件类,是Leaf和Composite的共有接口
class GeneralComponent
{
public:    
    
//为了确保调用到析构函数,此处声明为虚函数    
    virtual ~GeneralComponent(){}   
    
    
//由于General中不实现Add,Remove,GetChild,此处需要一个缺省的实现,但此处抛出异常更合适一些,为了简单,省略了。
    virtual void Add(GeneralComponent* pComonent) {}    
    
virtual void Remove(GeneralComponent* pComonent) {}    
    
virtual GeneralComponent* GetChild(int i) {return NULL;}    
    
virtual void Assault() = 0;
};

//具体用到的类
class General : public GeneralComponent
{
private:    
    
string m_strName;

public:    
    General(
string strName):m_strName(strName){}    

    void Assault()
    {        
        cout 
<< m_strName << " 进入战斗!" << endl;    
    }    
};

//组合类
class GeneralComposite : public GeneralComponent
{
private:    
    
string m_strName;  
    
    
//用来存放需要组合的对象    
    vector m_pComponents;

public:
    GeneralComposite(
string strName):m_strName(strName){}    
    
    
virtual ~GeneralComposite()    
    {        
        vector::iterator pos;    
        
for( pos = m_pComponents.begin();pos::iterator ivite= find(m_pComponents.begin(),m_pComponents.end(),pGeneral);    
            m_pComponents.erase(ivite);    

    }    
    
    GeneralComponent* GetChild(int i)
    {        
        
return m_pComponents[i];
    }    
    
    
void Assault()
    {        
        cout 
<< m_strName << "战斗序列" << endl;  
        
        
//需要调用所有的组合对象的操作        
        vector::iterator pos;
        
for( pos = m_pComponents.begin();posAssault();        
    }    
}
};

    我们再来看一下,客户端如何来调用该对象: 

int main(int argc, char* argv[])
{    
    GeneralComposite pArmy(
"梁山大军");    
    GeneralComposite
* pHorseArmy = new GeneralComposite("梁山马军");    
    GeneralComposite
* pPaceArmy = new GeneralComposite("梁山步军");    
    General 
*pWusong = new General("好汉武松");    
    General 
*pHuaheshang = new General("侠客鲁智深");    
    General 
*pLida = new General("莽汉李逵");    
    General 
*pLinchong = new General("英雄林冲");    
    General 
*pGuansheng = new General("大刀关胜");

    pHorseArmy->Add(pLinchong);
    pHorseArmy
->Add(pGuansheng);    
    pPaceArmy
->Add(pWusong);
    pPaceArmy
->Add(pHuaheshang);    
    pPaceArmy
->Add(pLida);    
    pArmy.Add(pHorseArmy);    
    pArmy.Add(pPaceArmy);    
    pArmy.Assault();    
    
return 0;
}

运行结果如下:

     我们可以看到,通过组合模式,对于对象的调用变得简单了起来,只要通过一个函数就可以实现对所有对象的统一调用。这样做最大的好处就是将所有的代码统一成了一份,可以避免在不同的地方出现类似的代码。在多人合作的情况下,由于交流不充分的问题,很多时候开发人员各自写各自的代码,而不关心别人的代码,极容易产生相同功能的代码。当我们的类进行扩展的时候,可能很多地方都需要进行修改。
    我们再来看看组合模式可能存在的问题。Leaf类继承了Component中的所有方法,但对于Add,Remove,GetChild等操作,这些操作对它来说是无意义的。我们也可以把这些操作放到Composite中,但在这种情况下,我们将无法通过基类来直接操作Composite对象。个人觉得这个问题可以根据具体问题权衡解决。我们也可以为Component扩展更多的Leaf或者Composite,在这种情况下,如果我们想控制Composite中只能添加限定的Leaf或者Composite在静态情况下是不可能的,必须在运行时刻去判断。
    还有,我们应该最大化Component接口,我们使用该接口的目的就是使用户不知道它具体操作的是哪个Leaf或者Composite,所以我们应该尽量多定义一些它们公共的操作。这样还是会造成我们上面提到的会给一些类带来无意义的功能。
    我们再来简单比较一下Composite和Decorator模式,这两个模式都会有一个子类拥有基类的指针,不同之处是Composite会拥有多个组件,而Decorator只拥有一个组件。Composite主要目的是对象的聚集,使它们的调用统一化;而Decorator不是此目的,它的目的是给对象添加一些额外的功能。
    好了,Composite的讲解先到此为止。如果你感兴趣的话,可以试着把我们第一章用到的策略模式应用到该处。不过我们下面看另外一个问题。在main中,我们需要很多代码去完成对象的创建。我们是否可以将对象的创建部分也做一些封装呢?我们可以找找创建型的模式中是否有适合我们的模式?
    在《设计模式》中,我们可以找到Builder模式,它的定义是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。和我们的意图有点象,先来看看《设计模式》中为我们提供的类图:

     作一下解释:
    1, Director是具体要用到复杂对象的部分(如GeneralComponent)的类(我们程序中是main中调用)。它会通过Builder来建造自己需要的对象,而不需要自己通过new来得到所要的对象。
    2, 而Builder的是一个抽象的创建对象的接口。
    3, ConcreteBuilder是我们用来具体创建对象的类。它可能有多个,用来帮我们建造不同类型的对象。如我们把我们的General派生出不同的风格,如先秦风格和罗马风格的时候,我们可能需要生成不同的类。而每次创建一种风格的对象,我们就可以扩展Builder而生成不同的创建类。
    我们的例子比较简单,不需要这么多东西。Builder的重要特征是:它是用来一步一步创建复杂对象类型的。我们的组合类由于需要组合不同的组件,所以整个创建过程比较复杂,那么可以借用Builder模式的特点来创建我们的对象。好的,看看具体的代码:

class Builder
{
public:    
    
static GeneralComponent* CreateCompise(GeneralComponent *pParent,string strArr[],int iLength = 1)    
    {        
        
if ( pParent != NULL )    
        {            
            
forint i = 0 ; i < iLength ; i++ )    
            {                
                pParent
->Add(new GeneralComposite(strArr[i]));    
            }            
            
return pParent;    
        }        
        
else    
        {        
            
return new GeneralComposite(strArr[0]);        
        }
    }    
    
    
static GeneralComponent* CreateGeneral(GeneralComponent *pParent,string strArr[],int iLength = 1)    
    {        
        
if ( pParent != NULL )        
        {            
            
forint i = 0 ; i < iLength ; i++ )    
            {                
                pParent
->Add(new General(strArr[i]));    
            }            

            return pParent;        
        }        
        
else    
        {        
            
return new General(strArr[0]);    
        }
    }
};

 这边的代码略显简单,只重意了,你可以看到,创建过程被封装成CreateCompise和CreateGeneral两个方法,通过它们来创建我们所需要的对象。只要将所需要的Composite(为它创建Leaf和子Composite的),还有创建对象所需要的参数(例子有些简单,只要一个初始化参数)数组和数组长度传进函数就可以帮我们创建所需要的对象了。由于Builder没有成员变量,将两个函数设置成了静态函数,想当于一个单件类型。我们再看看它的具体用法:

int main(int argc, char* argv[])
{        
    
string strArmy[1= {"梁山大军"};    
    
string strArmyChild[2= {"梁山马军","梁山步军"};    
    
string strSpecLeader[3= {"好汉武松","侠客鲁智深","莽汉李逵"};    
    
string strHorseLeader[2= {"英雄林冲","大刀关胜"};

    GeneralComponent *pArmy = Builder::CreateCompise(NULL,strArmy);
    Builder::CreateCompise(pArmy,strArmyChild,
2);    
    Builder::CreateGeneral(pArmy
->GetChild(0),strHorseLeader,2);    
    Builder::CreateGeneral(pArmy
->GetChild(1),strSpecLeader,3);
    pArmy
->Assault();    
    delete pArmy;
}

     这样我们就可以统一对象的创建过程。做到一定程度的聚合。我个人感觉模式的学习过程是从形到意(就是从一些具体的例子看起,通过具体的例子来理解模式);而后是重意不重形(明白模式的意义之后,我们就可以随意使用模式了,而不需要硬套公式)。所以此处使用了和书上并不一致的Builder模型。其实Builder模式与Abstract Factory模式很相似,我们下次描述Abstract Factory模式的时候,再回头比较一下。 
 

抱歉!评论已关闭.