九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

序列化支持(4)—Boost的序列化库的强大之处

序列化支持(4)—Boost的序列化库的强大之处

write by 九天雁翎(JTianLing) – www.jtianling.com

讨论新闻组及文件

1.      非介入式版本

感觉实际使用中我还没有碰到过,既然需要完全的public才能还原数据,那么介入不介入好像影响也不大了,除非碰到一个东西是别的公司写的,不让改,我是还没有碰到这样的情况。

 

从这里开始见识Boost序列化库的强大。。。。。。。。

 

2.      指针的序列化:

下面的例子为文档中例子的简化,并添加必要部分以方便运行及演示。

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <fstream>

// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
    friend class boost::serialization::access;
    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive
    // 是一个输入存档,则操作符& 被定义为>>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & degrees;
       ar & minutes;
       ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};

class bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    { 
       ar & latitude;
       ar & longitude;
    }

    gps_position latitude;
    gps_position longitude;
public: 
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
         latitude(lat_), longitude(long_){ }
          
    virtual ~bus_stop(){ }
};

class bus_stop_corner : public bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       // 序列化基类信息
       ar & boost::serialization::base_object<bus_stop>(*this);
       ar & street1;
       ar & street2;
    }
    std::string street1;
    std::string street2;

public:
    bus_stop_corner(){}
    bus_stop_corner(const gps_position & lat_, const gps_position & long_,
       const std::string & s1_, const std::string & s2_
       ) :
    bus_stop(lat_, long_), street1(s1_), street2(s2_)
    {}

    virtual std::string description() const
    {
       return street1 \+ " and " \+ street2;
    }
};


class bus_route
{
    friend class boost::serialization::access;
    bus_stop_corner * stops[2];
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       int i;
       for(i = 0; i < 2; ++i)
           ar & stops[i];
    }

public:
    bus_route(bus_stop_corner *apStop1, bus_stop_corner *apStop2)
    {
       stops[0] = apStop1;
       stops[1] = apStop2;
    }
    bus_route(){}
};



int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");

    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);

    bus_stop_corner *lpStop1 = new bus_stop_corner(latitude, longitude, "corn1", "corn2");
    bus_stop_corner *lpStop2 = new bus_stop_corner(latitude, longitude, "corn3", "corn4");

    bus_route route(lpStop1, lpStop2);

    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }

    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }

    delete lpStop1;
    delete lpStop2;
    return 0;
}

 

这里的强大之处在于指针反序列化的时候自动的分配了内存,这样简化了很多的操作,当然,这样就会存在文档中提出的内存泄露的问题,在此例中的确存在,反序列化时分配了内存但是却没有合理的地方去释放,由外部去释放感觉并不是太妥当,boost文档中的建议是使用智能指针,比如share_ptr。这个例子我们放到最后,先看看利用普通类的一条确保内存分配并不泄露的原则,哪里分配的哪里释放,对象自己管理自己的内存。

见下例:(此例为文档中没有的)

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <fstream>

// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
    friend class boost::serialization::access;
    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive
    // 是一个输入存档,则操作符& 被定义为>>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & degrees;
       ar & minutes;
       ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};

class bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    { 
       ar & latitude;
       ar & longitude;
    }

    gps_position latitude;
    gps_position longitude;
public: 
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
         latitude(lat_), longitude(long_){ }
          
    virtual ~bus_stop(){ }
};

class bus_stop_corner : public bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       // 序列化基类信息
       ar & boost::serialization::base_object<bus_stop>(*this);
       ar & street1;
       ar & street2;
    }
    std::string street1;
    std::string street2;

public:
    bus_stop_corner(){}

    bus_stop_corner(const gps_position & lat_, const gps_position & long_,
       const std::string & s1_, const std::string & s2_
       ) :
    bus_stop(lat_, long_), street1(s1_), street2(s2_)
    {}

    virtual std::string description() const
    {
       return street1 \+ " and " \+ street2;
    }
};


class bus_route
{
    friend class boost::serialization::access;

    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了
    bus_stop_corner * stops[2];
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       for(int i = 0; i < 2; ++i)
           ar & stops[i];
    }

public:
    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2)
    {
       stops[0] = new bus_stop_corner(aoStop1);
       stops[1] = new bus_stop_corner(aoStop2);
    }

    bus_route()
    {
       stops[0] = new bus_stop_corner;
       stops[1] = new bus_stop_corner;
    }

    ~bus_route()
    {
       for(int i = 0; i < 2; ++i)
       {
           delete stops[i];
       }
    }
};



int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");

    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);

    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");
    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");

    bus_route route(loStop1, loStop2);

    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }

    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

 

其实在一般情况下,只需要遵循了上述的原则,内存泄露问题一般不会存在,但是这里有个疑问就是,当指针分配了内存,boost序列化的时候是不是还是傻傻的去重新分配一次内存,然后导致第一次分配的内存没有正常释放,导致内存泄露呢?我们来检验一样。

当new_route调用默认构造函数分配内存时,数组中指针的地址如下:

[0] = 0x003b7090 {street1="" street2="" }
[1] = 0x003bafb0 {street1="" street2="" }

反序列化后:

[0] = 0x003b9150 {street1="corn1" street2="corn2" }
[1] = 0x003b9268 {street1="corn3" street2="corn4" }

经证实。。。。。boost在指针已经分配过内存的情况下仍然重新为指针分配了一次内存,这一点够傻的,那么,这样傻的行为有没有一点保护呢?比如首先判断一下指针是否为NULL,然后先delete再new呢?虽然这样的操作好像更傻。。。当时总好过与内存泄露,验证一下的方法很简单,在ia » new_route;一句执行前在bus_stop_corner的析构函数上加断电,假如boost调用了delete,其析构一定会发生,事实是残酷的。。。。假如事先为指针分配了内存,那么必然发生内存的泄露。。。。boost根本不管指针是否已经分配过内存,直接忽略,并重新分配内存。

 

其实,一般而言,需要反序列化的时候,提供一个空的对象也是比较合理的,毕竟这是一个还原对象的过程,所以程序改成下面例子这样就可以在不使用智能指针的时候避免内存泄露了。

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <fstream>

// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <cstdlib>
using namespace std;

/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
    friend class boost::serialization::access;
    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive
    // 是一个输入存档,则操作符& 被定义为>>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & degrees;
       ar & minutes;
       ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};

class bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    { 
       ar & latitude;
       ar & longitude;
    }

    gps_position latitude;
    gps_position longitude;
public: 
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
         latitude(lat_), longitude(long_){ }
          
    virtual ~bus_stop(){ }
};

class bus_stop_corner : public bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       // 序列化基类信息
       ar & boost::serialization::base_object<bus_stop>(*this);
       ar & street1;
       ar & street2;
    }
    std::string street1;
    std::string street2;

public:
    bus_stop_corner(){}
    bus_stop_corner(const gps_position & lat_, const gps_position & long_,
       const std::string & s1_, const std::string & s2_
       ) :
    bus_stop(lat_, long_), street1(s1_), street2(s2_)
    {}

    virtual std::string description() const
    {
       return street1 \+ " and " \+ street2;
    }

    ~bus_stop_corner()
    {
    }
};


class bus_route
{
    friend class boost::serialization::access;

    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了
    bus_stop_corner * stops[2];
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       for(int i = 0; i < 2; ++i)
           ar & stops[i];
    }

public:
    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2)
    {
       stops[0] = new bus_stop_corner(aoStop1);
       stops[1] = new bus_stop_corner(aoStop2);
    }

    bus_route()
    {
       stops[0] = NULL;
       stops[1] = NULL;
    }

    ~bus_route()
    {
       for(int i = 0; i < 2; ++i)
       {
           if(stops[i] != NULL)
           {
              delete stops[i];
           }
       }
    }
};



int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");

    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);

    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");
    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");

    bus_route route(loStop1, loStop2);

    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }

    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

 

这里的bus_route类有指针和内存分配但是没有合理的拷贝构造函数和operator=重载,仅仅只是作为演示使用,实际中这里几乎是必须的,即使不需要使用到复制也应该将此两个函数放入private中以表示禁止复制,以防误用。(比如std的I/O stream类实现即是如此)

 

改成上述例子中的形式后,需要注意的是反序列化前一定要是一个空对象,假如以前有分配内存的话需要提前释放到,还好这些都可以很简单的由对象本身所保证。这一点可能的错误应用应该算是Boost为了易用性而导致的。。。。基本掌握了还算能接受,起码对于指针的序列化还是简单了很多,仅仅是需要多注意一下传入的必须是空的指针就行。

 

Boost作为准标准库,虽然有的时候显得有点庞大,但是STL的搭配,和众多新Boost特性的搭配是非常的默契(不知道这个词是否恰当)。从上面的序列化就可以看出来,序列化是完全融入原有的C++ stream体系的,这点我们公司的序列化根本没有办法比。谈到这点就是想说,其实包括auto_ptr甚至shared_ptr在内的智能指针,包括vector,map等STL容器,甚至连array在内。

 

这里是使用智能指针的两个例子,但是boost的serialize库如此偏心。。。。。share_ptr是内嵌在库里面的,而C++标准库的auto_ptr竟然没有内嵌在库里面,仅仅是在demo中给出实现。。。。。。也就是说,明明实现了,就是不想放到库里面去。。。。。如此不推荐使用auto_ptr的做法,完全见证了我当时强烈感叹的auto_ptr的异类。。。。我就不知道他是怎么混进标准库的。

 

以下是智能指针的使用示例代码:

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <fstream>

// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <cstdlib>
#include <boost/tr1/memory.hpp>
#include <vector>
#include <map>
#include <boost/tr1/unordered_map.hpp>
#include <memory>
#include <boost/serialization/shared_ptr.hpp>
using namespace std;
using namespace boost;
using namespace boost::serialization;

#include <boost/serialization/split_free.hpp>

namespace boost { 
    namespace serialization {

       /////////////////////////////////////////////////////////////
       // implement serialization for auto_ptr<T>
       // note: this must be added to the boost namespace in order to
       // be called by the library
       template<class Archive, class T>
       inline void save(
           Archive & ar,
           const std::auto_ptr<T> &t,
           const unsigned int file_version
           ){
              // only the raw pointer has to be saved
              // the ref count is rebuilt automatically on load
              const T * const tx = t.get();
              ar << tx;
       }

       template<class Archive, class T>
       inline void load(
           Archive & ar,
           std::auto_ptr<T> &t,
           const unsigned int file_version
           ){
              T *pTarget;
              ar >> pTarget;
              // note that the reset automagically maintains the reference count
#if BOOST_WORKAROUND(BOOST_DINKUMWARE_STDLIB, == 1)
              t.release();
              t = std::auto_ptr<T>(pTarget);
#else
              t.reset(pTarget);
#endif
       }

       // split non-intrusive serialization function member into separate
       // non intrusive save/load member functions
       template<class Archive, class T>
       inline void serialize(
           Archive & ar,
           std::auto_ptr<T> &t,
           const unsigned int file_version
           ){
             boost::serialization::split_free(ar, t, file_version);
       }

    } // namespace serialization
} // namespace boost


/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
    friend class boost::serialization::access;
    // 如果类Archive 是一个输出存档,则操作符& 被定义为<<.  同样,如果类Archive
    // 是一个输入存档,则操作符& 被定义为>>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & degrees;
       ar & minutes;
       ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position()
    {
       degrees = 0;
       minutes = 0;
       seconds = 0.0;
    };
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};

class bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    { 
       ar & latitude;
       ar & longitude;
    }

    gps_position latitude;
    gps_position longitude;
public: 
    bus_stop(){ }
    bus_stop(const gps_position & lat_, const gps_position & long_) :
         latitude(lat_), longitude(long_){ }
          
    virtual ~bus_stop(){ }
};

class bus_stop_corner : public bus_stop
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       // 序列化基类信息
       ar & boost::serialization::base_object<bus_stop>(*this);
       ar & street1;
       ar & street2;
    }
    std::string street1;
    std::string street2;

public:
    bus_stop_corner(){}
    bus_stop_corner(const gps_position & lat_, const gps_position & long_,
       const std::string & s1_, const std::string & s2_
       ) :
    bus_stop(lat_, long_), street1(s1_), street2(s2_)
    {}

    virtual std::string description() const
    {
       return street1 \+ " and " \+ street2;
    }

    ~bus_stop_corner()
    {
    }
};


class bus_route
{
    friend class boost::serialization::access;

    // 这里将数组缩减到,是为了减少编码量并显得更清楚,毕竟说明清楚情况就好了
    shared_ptr<bus_stop_corner> msptrBusStop;
    auto_ptr<bus_stop_corner> maptrBusStop;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & msptrBusStop;
       ar & maptrBusStop;
    }

public:
    bus_route(const bus_stop_corner& aoStop1, const bus_stop_corner& aoStop2):
     msptrBusStop(new bus_stop_corner(aoStop1)),
       maptrBusStop(new bus_stop_corner(aoStop2))
    {
    }

    bus_route()
    {

    }

    ~bus_route()
    {
    }
};



int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("bus_route");

    // 创建类实例
    const gps_position latitude(1, 2, 3.3f);
    const gps_position longitude(4, 5, 6.6f);

    bus_stop_corner loStop1(latitude, longitude, "corn1", "corn2");
    bus_stop_corner loStop2(latitude, longitude, "corn3", "corn4");

    bus_route route(loStop1, loStop2);

    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << route;
       // 在调用析构函数时将关闭存档和流
    }

    // ... 晚些时候,将类实例恢复到原来的状态
    bus_route new_route;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("bus_route", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> new_route;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

至于剩下的STL容器之类的,由于和普通的成员变量都看不出区别了,我弄个简单的示例说明一下就好了,按原文档的话来说是:

The serialization library contains code for serialization of all STL classes.

还不够吗?

从上面的例子上已经可以看出std::string肯定没有问题了,我只想知道一个东西,tr1的unordered_map实现了没有。但是非常遗憾。。。。。呵呵,没有实现,不过vector和list的实现自然是没有问题,一下给出例子,这里需要说明的是,对于STL来说,可能是考虑到每个实现都比较大,所以在使用了相应的容器后,需要包含序列化相应的头文件,比如vector就是boost/serialization/vector.hpp,依次类推,这点share_ptr的示例其实就已经使用了,但是没有特别说明。

// BoostLearn.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <fstream>

// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <cstdlib>
#include <boost/tr1/memory.hpp>
#include <vector>
#include <map>
#include <memory>
#include <boost/serialization/vector.hpp>
using namespace std;
using namespace boost;
using namespace boost::serialization;

class CSerializeAble
{
public:
    std::vector<int> miVec;

    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & miVec;
    }
};
int main() {
    // 创建并打开一个输出用的字符存档
    std::ofstream ofs("contains");

    CSerializeAble loSA;
    loSA.miVec.push_back(1);
    loSA.miVec.push_back(2);
    loSA.miVec.push_back(3);

    // 保存数据到存档
    {
       boost::archive::text_oarchive oa(ofs);
       // 将类实例写出到存档
       oa << loSA;
       // 在调用析构函数时将关闭存档和流
    }

    // ... 晚些时候,将类实例恢复到原来的状态
    CSerializeAble lonewSA;
    {
       // 创建并打开一个输入用的存档
       std::ifstream ifs("contains", std::ios::binary);
       boost::archive::text_iarchive ia(ifs);
       // 从存档中读取类的状态
       ia >> lonewSA;
       // 在调用析构函数时将关闭存档和流
    }
    return 0;
}

 

到了这里,已经可以看到Boost::Serialize的强大了,这里还想说明的是,虽然文档中有std::list<bus_stop *> stops;这样的例子,但是实际上我这样做却无法编译通过。。。。。。。。这点很奇怪

然后,Boost::Serialize由于是完全融入C++的IOStream系统的,所以,只要你实现你自己的Stream,比如文档中提到的XML系统,你就可以实现任意的方式保存你序列化的数据。这样的开放性也是值得一提的,光是这一点都不是一般的非“准标准库”可以比拟的。。。。。。。。序列化大概就讲这么多吧。。。。序列化在公司只用了半天去了解,但是自己这段时间工作太忙,结果学习Boost的序列化库的使用,还不是看其实现,就用了这么久,真是无奈。。。。

 

write by九天雁翎(JTianLing) – www.jtianling.com

分类:  网络技术 
标签:  Boost  C++  序列化 

Posted By 九天雁翎 at 九天雁翎的博客 on 2009年04月21日

前一篇: Debug思考模式(1)—关注更改 后一篇: Observer模式的升级版,Event通知实现