你带酒来,我有故事

C++设计模式之Prototype模式

:: 代码生涯 二十画生 1019℃ 0评论

首先引入《C++ Common Knowledge Essential Intermediate Programming》(中文翻译叫《C++必知必会》)中条款29 虚构造函数与Prototype模式中的一个例子:

假如你现在身处一家瑞典餐馆,并且想点菜。不幸的是,你的瑞典水平仅仅局限于技术领域或“粗口”(一般是二者的结合)。菜单是用瑞典文写的,你看不懂,但是你注意到餐馆对面的一位绅士正在享受美餐。于是,你把侍者叫过来,说道:

如果那位先生在吃鱼的话,我就点一份鱼。如果他在吃意大利面条的话,我就要一份意大利面条。如果他在吃鳗鱼的话,我就要一份鳗鱼。如果他在金橘蜜饯的话,我就点一份金橘蜜饯。

这合理吗?当然不(别的先不说,你可能并不想在一家瑞典餐馆里点一份意大利面条)。这个“程序”存在两个基本的问题。首先,这种方式冗长乏味而且低效。其次,这种问询可能会以失败而告终。如果你把这一系列问题问完了,还是没有猜到那位先生在吃什么,结果会怎样?侍者将会离开,留下饥肠辘辘的你独自在那儿一筹莫展。退一步说,即使你碰巧知道菜单的全部内容,从而可以确保最终点餐成功,但如果你下一次去那家餐馆,菜单已经被改掉了,你的问题列表就变得失效或不完善了。

显然,恰当的方式就是把侍都叫过来并且说:“我想要对面那位先生正在吃的东西。”

如果这位侍者拘泥于字面含义理解你的意思,他将会过去把那位先生吃了一半的食物夺下来并端到你的桌子上。然而,这样的行为很伤感情甚至可能会导致一场食物争夺战。当两个用餐者试图在同一时间消费同样的食物时,就有可能发生这种不愉快的事情。如果侍者对自己的业务很熟悉,他将会给你一份那位先生享用食物的“复制品”,且不会对被“复制”的食物状态造成任何影响。

有两个主要的原因需要使用“克隆”:必须(或者更喜欢)对正在处理的对象的精确类型保持“不知情”;不希望改变被克隆的原始对象,也不希望受原始对象的影响。

在C++中,提供了这种克隆对象的能力的成员函数,从传统上说,被称为“虚构造函数”。当然,并不存在什么虚构造函数,但是生成对象的一份复制品通常涉及到通过一个虚函数对其类的构造函数的间接调用,因此,即使不是真的虚构造函数,效果上也算是虚构造函数了。最近,这种技术已经被作为Prototype模式的一个实例。

当然,我们必须了解所指向的那个对象。在这个例子中,只要知道所需的是一个Meal就可以了。

class Meal
{
    public:
        virtual ~Meal();
        virtual void eat() = 0;
        virtual Meal *clone() const = 0;
        //...
};

Meal类型通过clone成员函数提供了克隆能力。clone函数实际上是一种专门类型的Factory Method模式,它制造一个适当的产品,同时允许调用代码对上下文和产品类的精确类型保持不知情。派生于Meal的具体类(也就是那些实际存在的并且列在菜单上的Meal)必须为纯虚clone操作提供一个实现:

class Spaghetti : public Meal
{
    public:
        Spaghetti(const Spaghetti&);      //复制构造函数
        void eat();
        Spaghetti *clone() const
        {
            return new Spaghetti(*this);  //调用复制构造函数
        }
        //...
};

有了这个简单的框架,就可以生成任何类型的Meal的复制品,并且无需知道正在复制的Meal的实际类型的细节。注意,以下代码没有提及具体派生类,因而代码不会和任何当前(或将来出现)的Meal派生类相耦合。

const Meal *m = thatGuysMeal();    // 不管他正在吃什么东西......
Meal *myMeal = m->clone();         // 我都要一份和他一样的!

事实上,最终我们完全有可能点了一些从来没有听过的食物。实际上。使用Prototype模式,对一个类型的存在不知情并不会创建该类型的对象造成任何障碍。上面的多态代码可能通过编译并发布,如果以后增加新的Meal类型来增加功能也不需要重新编译。

这个例子例证了软件设计中“不知情”的一些优点,尤其是在用于定制和扩展的框架结构软件设计中。记住:有些事情你知道的越少越好!

通过上面可以看出,Prototype实际上得到的是一份拷贝,而且被拷贝的东西一般是你不知情的。拷贝,C++中有一个很好的成员函数,即复制构造函数,以供选择。谈到复制构造函数,一般会谈到深拷贝与浅拷贝,等下在Prototype例子中我会给出深拷贝的例子。

Prototype模式的典型结构图为:

首先针对面向对象UML中类的关系做下说明,类和对象的、类的关系可以从以下两个方向进行分类说明:

纵向关系:继承、实现

横向关系:依赖、关联、聚合、组合

UML示意图分别如下:

纵向关系:

继承                                                             实现

             

Generalize(泛化)                                               Realize(实现)

横向关系:

             

Dependency(依赖)                                            Association(关联)

              

Aggregation(聚合)                                                Composition(组合)

至于具体关系的意思请参考相关UML相关书集。

具体的Prototype例子(加入了深拷贝问题)如下:

//Prototype.h
#ifndef PROTOTYPE_H
#define PROTOTYPE_H

class Prototype
{
public:
    virtual ~Prototype();
    virtual Prototype *clone() const = 0;

protected:
    Prototype();
};

class ConcretePrototype : public Prototype
{
public:
    ConcretePrototype(int x = 0, char *name = 0);
    ConcretePrototype(const ConcretePrototype& rhs);
    ConcretePrototype &operator=(const ConcretePrototype &rhs);
    ~ConcretePrototype();

public:
    Prototype *clone() const;
    //ConcretePrototype *clone() const;
    void set_x(const int x);
    int get_x()const;
    void set_name(const char *name);
    char *get_name() const;

private:
    int _x;
    char *_name;
};

#endif
//Prototype.cpp
#include "Prototype.h"
#include <string.h>

//Prototype
Prototype::Prototype()
{

}

Prototype::~Prototype()
{

}

//ConcretePrototype
ConcretePrototype::ConcretePrototype(int x, char *name)
{
    _x = x;

    if(name != 0)
    {
        _name = new char[strlen(name) + 1];
        //strcpy(_name, name);
        strncpy(_name, name, strlen(name));
        _name[strlen(name)] = '\0';
    }
}

ConcretePrototype::ConcretePrototype(const ConcretePrototype& rhs)
{
    _x = rhs._x;

    _name = new char[strlen(rhs._name) + 1];
    //strcpy(_name, rhs._name);
    strncpy(_name, rhs._name, strlen(rhs._name));
    _name[strlen(rhs._name)] = '\0';
}

ConcretePrototype &ConcretePrototype::operator=(const ConcretePrototype &rhs)
{
    if(this != &rhs)
    {
        _x = rhs._x;

        delete[] _name;
        _name = new char[strlen(rhs._name) + 1];
        //strcpy(_name, rhs._name);
        strncpy(_name, rhs._name, strlen(rhs._name));
        _name[strlen(rhs._name)] = '\0';
    }
    return *this;
}

ConcretePrototype::~ConcretePrototype()
{
    if(_name != NULL)
    {
        delete[] _name;
    }
}

int ConcretePrototype::get_x() const 
{
    return _x;
}

void ConcretePrototype::set_x(const int x)
{
    _x = x;
}
void ConcretePrototype::set_name(const char *name)
{
    if(name != NULL)
    {
        _name = new char[strlen(name) + 1];
        strncpy(_name, name, strlen(name));
        _name[strlen(name) + 1] = '\0';
    }
}

char *ConcretePrototype::get_name() const
{
    return _name;
}

Prototype *ConcretePrototype::clone() const
{
    return new ConcretePrototype(*this);
}
//main.cpp
#include <iostream>
#include "Prototype.h"
using namespace std;

int main()
{
    Prototype *pp = new ConcretePrototype(100, "chenyuming");
    Prototype *pp2 = pp->clone();

    cout<<static_cast<ConcretePrototype*>(pp2)->get_x()<<"\t"<<static_cast<ConcretePrototype*>(pp2)->get_name()<<endl;

    return 0;
}

运行结果:

100     chenyuming

最后要提下:我在Prototype.h中注释掉了ConcretePrototype *clone() const;这行代码,这是因为我用的是VC6.0来测试上述代码的,按照《C++必知必会》条款31 协变返回类型中谈到的,ConcretePrototype *clone() const是可以接受的,我估计是编译器的原因,麻烦各位帮我试下并告之。

转载请注明:二十画生 » C++设计模式之Prototype模式

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址