Chinaunix首页 | 论坛 | 博客
  • 博客访问: 572788
  • 博文数量: 155
  • 博客积分: 4015
  • 博客等级: 上校
  • 技术积分: 1625
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-18 16:55
文章分类

全部博文(155)

文章存档

2009年(20)

2008年(39)

2007年(66)

2006年(29)

2005年(1)

我的朋友

分类: 项目管理

2008-01-28 14:45:12

   原本在《设计模式--可复用面向对象团软件基础》(简称《设计模式》)书中介绍这个模式的时候选的例子太过复杂.倒不是因为GOF选材不好.而是本来这个模式就复杂,想要将该模式很好的应用的话,需要考虑的也较多.但这对模式的初学者并不好,往往是看着看着就迷茫了.而忘记的该模式本身想表达的意思.
   我倒想以个简单的方式来说明这个模式,分享一下经验.希望可以抛砖引玉,让更多的朋友可以亲近,或是不再害怕模式.
 
   1,概述.
 
   在《设计模式》中有几个原则:
  • 尽量采用组合,而不是继承.这是为什么?
   解耦合(Decoupling).
  • 那又为什么要解耦合呢?
   方便复用(Reusable).
 
这便符合《设计模式》本书想带给大家的收获----创建可复用的软件.原型(Prototype)这个模式给我们带来两个优势:
   其一是尽量采用组合;
   其二可以尽可能减少定义类的数量(注:多做多错,杨老师的口头禅,呵呵.)
如果一个类A仅依赖另一个类B,那么可以考虑使用原型(Prototype)模式.是不是说的有些迷糊,我们看一看实例.
 
   2,举例说明.
 
   我们拿做汉堡(Hamburger,也就是类似KFC的那种)为例.要做汉堡我们需要配料(Material)再加上两片面包.根据配料不同我们可以做出不同的汉堡,比如牛肉汉堡,蔬菜汉堡等等.
   经过一段时间的思考,我们可能这样定义出表示配料(Material)的类,以及表示汉堡的类(Hamburger).类图如下:
 
 
   从上图可以看出,当前我们定义了四个具体类(Concrete class).他们分别表示两个配料的类,和两种汉堡的类.
   也许看看代码,更容易理解一些.现给出第一次设计的完整代码.
 
 

//Platform: WinXp + VC6.0
#include <iostream.h>
#include <stdio.h>
/*-----------------表示配料的类-----------------------*/
class Material
{
protected:
    char *materialName;
public:
    virtual void Feed() = 0;
    char *MaterialName()
    {
        return materialName;
    }
};
class Vegetable:public Material
{
public:
    virtual void Feed()
    {
        Clean();
        cout<<"Added the vegetable..."<<endl;
        materialName = "Vegetable";
    }
    void Clean() //清洗蔬菜
    {
        cout<<"Cleaning the vegetable. "<<endl;
    }

};
class Beef:public Material
{
public:
    virtual void Feed()
    {
        Roast();
        cout<<"Added the beef..."<<endl;
        materialName = "Beef";
    }
    void Roast() //牛肉是需要烤一下

    {
        cout<<"Roasting the beef."<<endl;
    }
};
/*-----------------表示汉堡的类-----------------------*/
class Hamburger
{
public:
    virtual void MakeHamburger() = 0;
};

class BeefHam:public Hamburger
{
private:
    Beef _beef;
public:
    virtual void MakeHamburger()
    {
        _beef.Feed(); //添加配料

        cout<<_beef.MaterialName()<<"汉堡已经做好,请慢用."<<endl;
    }
};
class VegeHam:public Hamburger
{
private:
    Vegetable _vegetable;
public:
    virtual void MakeHamburger()
    {
        _vegetable.Feed(); //添加配料

        cout<<_vegetable.MaterialName()<<"汉堡已经做好,请慢用."<<endl;
    }
};


void main()
{
    cout<<"开始制作牛肉汉堡."<<endl;
    BeefHam beefHam;
    beefHam.MakeHamburger();

    cout<<endl;
    cout<<"开始制作蔬菜汉堡."<<endl;
    VegeHam vegeHam;
    vegeHam.MakeHamburger();
}

 
输出结果如下:

开始制作牛肉汉堡.
Roasting the beef.
Added the beef...
Beef汉堡已经做好,请慢用.
 
开始制作蔬菜汉堡.
Cleaning the vegetable.
Added the vegetable...
Vegetable汉堡已经做好,请慢用.

 
   这样设计的话,我们的复用性并不好.从UML图中我们可以看出.我们从Hamburger派生出的类依赖于Material的派生类即:Vegetable和Beef类.
   如果这时我们需要再做另外一种鸡肉汉堡的话,我们必须添加两个类,其中一个类继承于Material,再一个继承于Hamburger.即添加以下代码:
 

//派生自Material
class Chicken:public Material
{
public:
    virtual void Feed()
    {
        HandleChicken();
        cout<<"Added the beef..."<<endl;
        materialName = "Chicken";
    }
    void HandleChicken() //处理鸡肉
    {
        cout<<"Roasting the beef."<<endl;
    }
};
//派生自Hamburger
class ChickenHam:public Hamburger
{
private:
    Chicken _chicken;
public:
    virtual void MakeHamburger()
    {
        _chicken.Feed(); //添加配料
        cout<<_chicken.MaterialName()<<"汉堡已经做好,请慢用."<<endl;
    }
};

之所以需要如此,是因为ChickenHam这个类,紧紧依赖于Material的派生类Chicken.这反映在类图上就是被标为黄色的两个派生类:

 
也许这时大家发现了些端倪,类乎派生自Hamburger的类,除了各自拥有的配料(beef,chicken,vegetable)不一样以外,其它的并没有什么不同.冒似可以自动化生成Hamburger的派生类.
   如果你已想到这一步了,那离原型模式也就不远了.我们先给出原型模式的UML图示,让朋友们自己对号入座.并看看这带来优势----类似于自动生成一个具体(Concrete)的Hamburger类.这是一件美妙的事.
 
看一看,这个Prototype的图示.我们可以将配料类看做一个原型(Prototype),我们也为该原型定义一个virtual方法.让其可以Clone出一个具体的配料类.
   这会带来一个好处:我们在Client位置,即Hamburger类,只需参数化传递需要Clone的配料类即可,而不必我们每次都派生出一个具体化(Concrete)的Hamburger类.看起来类图是这样(注意,我将Material和Hamburger类位置左右调换了一下)
 
 
从图上可以看出,我们新增的Chicken类并没有新增一个相应的Hamburger类,而是定义了一个纯虚函数Clone,让其返回原型类(些处指Vegetable,Chicken或Beef)的引用.通过这些引用,我们可以不用派生Hamburger而直接是在Hamburger的方法中引用(或者说是参考)原型对象(Prototype Object).
 
   现在给出经过重新设计后,采用Prototype模式的完整Source.
 

//Platform: WinXp + VC6.0
#include <iostream.h>
class Material
{
protected:
    char *materialName;
public:
    virtual Material* Clone() = 0;
    virtual void Feed() = 0;
    char *MaterialName()
    {
        return materialName;
    }
};
class Vegetable:public Material
{
public:
    virtual Material* Clone()
    {
        return this;    
    }
    virtual void Feed()
    {
        cout<<"Added the vegetable..."<<endl;
        materialName = "Vegetable";
    }
};
class Beef:public Material
{
public:
    virtual Material* Clone()
    {
        return this;
    }
    virtual void Feed()
    {
        cout<<"Added the beef..."<<endl;
        materialName = "Beef";
    }
};

/*--------此处新增一个Material派生类即可----------*/
class Chicken:public Material
{
public:
    virtual Material* Clone()
    {
        return this;
    }
    virtual void Feed()
    {
        HandleChicken();
        cout<<"Added the beef..."<<endl;
        materialName = "Chicken";
    }
    void HandleChicken() //处理鸡肉

    {
        cout<<"Roasting the beef."<<endl;
    }
};

class Hamburger
{
private:
    Material *_pMaterial;   //优先采用组合,而不是继承
public:
    Hamburger(Material *pMaterial):_pMaterial(pMaterial)
    {}
    void MakeHamburger();
};
void Hamburger::MakeHamburger()
{
    //注意此处clone可以是深拷贝(deep copy)
    Material *pPrototype = _pMaterial->Clone();
    pPrototype->Feed();
    cout<<pPrototype->MaterialName()<<"汉堡已经做好,请慢用."<<endl;
}


void main()
{
    //创建汉堡配料
    Beef beef;
    Vegetable vegetable;


    cout<<"开始制作牛肉汉堡."<<endl;
    Hamburger beefHam(&beef);
    beefHam.MakeHamburger();

    cout<<endl;
    cout<<"开始制作蔬菜汉堡."<<endl;
    Hamburger vegeHam(&vegetable);
    vegeHam.MakeHamburger();

    cout<<endl;
    cout<<"开始制作鸡肉汉堡."<<endl;
    /*新增一个配料,然后当做参数传递即可*/
    Chicken chicken;
    Hamburger chickenHam(&chicken);
    chickenHam.MakeHamburger();

}

 
   3,总结.
  
   Protetype模式,本身似乎可以看做一个类型生成器(Class Generator).但又跟工厂方法(Factory Method)有些区别,工厂方法所产生的类,是我们已经在代码里显示定义过了的.而Prototype模式模似了(Simulate)了一组相关的派生类.
   原理简单但要是实施起来还是有很多地方需要细细考虑,单从原型类(Prototype class)的派生类(即,Concrete Prototype class)怎么样实现Clone这个方法来讲就非常的复杂.因为你不得不考虑"深拷贝"和"浅拷贝"(Shallow copy and deep copy)的情况.
 
   模式本身并不难,想要实现的目的也很明显.但在实际的项目中应用模式,用好模式.那还是相当有难度的.当你有一堆较烂的设计,你可以尝试通过重构(Refactoring),真至找到很好的解决方案.这些解决方案中,肯定会有模式的身影,不信你试试...
 
 
 
 
 
 
 
 
 
 
Jerry.Chou
    1/28'08
阅读(1240) | 评论(0) | 转发(0) |
0

上一篇:旧作--体验

下一篇:不做"超人"

给主人留下些什么吧!~~