C++实现Behavioral - Memento模式

来源:互联网 发布:乐敦养润水副作用知乎 编辑:程序博客网 时间:2024/05/17 07:00
 

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存其状态这样以后就可以将该对象恢复到原先保存的状态。

“Without violating encapsulation,capture and externalize an object’s internal state so that the object can be restored to this state later.” – GoF

 

动机

有时候需要记录一个对象的内部状态。比如要实现checkpoint或者undo这样的机制,可以让使用者从临时性的操作跳出来或者需要修复错误的时候,你必须将状态信息保存在某个地方,以便在进行某些操作后,将对象恢复到原来的状态。但通常情况下,对象封装了状态(即私有成员变量),因此其他的对象无法访问这些状态,而且也不可能将这些状态保存在对象之外。如果将这些状态设置成公有的,又会违反面向对象封装性的原则,同时也会削弱应用的可靠性和可扩展性。

 

在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个时刻的状态。如果使用一些共有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。Memento设计模式就可以实现对象状态的良好保存与恢复,但同时又不会因此而破坏对象本身的封装性。

 

UML类图:

 

角色

-         Memento

1.      保存Originator对象的内部状态。

2.      Originator外,其他对象均不能访问Memento对象。

-         Originator

1.      创建一个包含其当前内部状态快照的Memento对象。

2.      使用Memento对象来恢复其内部状态。

-         Caretaker

1.      Memento对象的容器。在C++中一般用stack来实现。

2.      从不对Memento对象的内容进行检查或操作。

 

示例代码:

// Memento.h

#include<iostream>

#include<stack>

usingnamespace std;

 

// CMemento类,用来保存CRectangle的状态

class CMemento

{

private:                                          // 所有的成员变量和成员函数都是私有的

        int topx;                                // 因此除友元类CRectangle外,其他对象都无法访问

        int topy;

        int width;

        int height;

 

private:

        CMemento()

        {

        }

 

//private:                                                                    // 1. 如果编写了显式拷贝构造函数,那么,它必须是公有的,

//      CMemento(const CMemento& memo)     //   否则CMementoStack将无法调用该拷贝构造函数。

//      {                                                                        // 2. 如果没有显式的拷贝构造函数,那么缺省的拷贝构造函数总是公有的。

//                topx = memo.topx;                               // 3. Memento模式中,如果仅考虑保存一次状态,则

//                topy = memo.topy;                               //    CMementoStack是不必要的,那么拷贝构造函数,可以

//                width = memo.width;                          //    显式地声明为private的,尽管在CRectanglecreate_memento

//                height = memo.height;                       //    成员函数中也会调用CMemento的拷贝构造函数,但CRectangle

//      }                                                                        //    CMemento的友元类,因此不存在这方面的限制。

 

private:

        void set_state(int topx,int topy, int width,int height)          // 保存CRectangle的状态

        {

                  this->topx = topx;

                  this->topy = topy;

                  this->width = width;

                  this->height = height;

        }

 

        friend class CRectangle;          // 友元类CRectangle,可以访问CMemento中的所有内容

};

 

// CRectangle类。一个矩形,需要保存状态改变的类

class CRectangle

{

private:

        int topx;                       // 矩形左上角的x坐标

        int topy;                       // 矩形左上角的y坐标

        int width;                    // 矩形的宽

        int height;                   // 矩形的高

 

public:

        CRectangle(int topx, int topy, int width, int height):topx(topx), topy(topy), width(width), height(height)

        {

        }

 

        // 模拟移动矩形的位置到指定的点,即改变了矩形的状态

        void move_to(int topx,int topy)

        {

                  this->topx = topx;

                  this->topy = topy;

        }

 

        // 模拟改变矩形的长和宽,即改变了矩形的状态

        void change_width_height(int width,int height)

        {

                  this->width = width;

                  this->height = height;

        }

 

        // 将矩形恢复到memo中所保存的状态

        void set_memento(CMemento memo)

        {

                  this->topx = memo.topx;

                  this->topy = memo.topy;

                  this->width = memo.width;

                  this->height = memo.height;

        }

 

        // 将矩形的状态保存到一个CMemento对象

        CMemento create_memento()

        {

                  CMemento cm;

                  cm.set_state(this->topx, this->topy, this->width, this->height);

                  return cm;

        }

 

        // 输出矩形的状态信息

        void print_info()

        {

                  cout << "Top left point's x coordinate: " << topx << endl;

                  cout << "Top left point's y coordinate: " << topy << endl;

                  cout << "The width is: " << width << endl;

                  cout << "The height is: " << height << endl;

        }

};

 

// CMemento对象的容器,可以用来保存多个CMemento对象,通常用stack来实现

class CMementoStack

{

private:

        stack<CMemento> stk;

 

public:

        void add_memento(CMemento memo)

        {

                  stk.push(memo);                        //CMemento对象压入栈中

        }

 

        CMemento get_memento()

        {

                  CMemento cm = stk.top();         //取得CMemento对象。这个过程会用到CMemento类的拷贝构造函数,

                                                                          // 由于CMemento对象中的成员变量均是普通类型(非指针、非类对象)

                                                                          // 因此使用默认的拷贝构造函数即可

                  stk.pop();                                      // 删除已经取得的CMemento对象

 

                  return cm;

        }

};

 

// Memento.cpp

#include"Memento.h"

 

int main(int argc,char **argv)

{

        CRectangle cr(10, 10, 100, 100);

        CMementoStack cs;

 

        cout << "Initial states: " << endl;

        cr.print_info();

 

        CMemento cm0 = cr.create_memento();           //将状态保存到CMemento对象

        cs.add_memento(cm0);                                       // CMemento对象压栈

 

        // 第一次改变状态

        cr.change_width_height(200, 200);                   // 改变矩形的高度和宽度

        cr.move_to(20, 20);                                                // 改变矩形的位置

        cout << "\nAfter 1st states changed: " << endl;

        cr.print_info();

        CMemento cm1 = cr.create_memento();           //将状态保存到CMemento对象

        cs.add_memento(cm1);                                       // CMemento对象压栈

 

        // 第二次改变状态

        cr.change_width_height(300, 300);                   // 改变矩形的高度和宽度

        cr.move_to(30, 30);                                                // 改变矩形的位置

        cout << "\nAfter 2nd states changed: " << endl;

        cr.print_info();

        // ... 这里不再压栈

 

        // 恢复到第一次状态的改变

        cr.set_memento(cs.get_memento());

        cout << "\nStates restored to 1st change: " << endl;

        cr.print_info();

 

        // 恢复到初始状态

        cr.set_memento(cs.get_memento());

        cout << "\nStates restored to initial: " << endl;

        cr.print_info();

}

 

运行结果:

Initial states:

Top left point's x coordinate: 10

Top left point's y coordinate: 10

The width is: 100

The height is: 100

 

After 1st states changed:

Top left point's x coordinate: 20

Top left point's y coordinate: 20

The width is: 200

The height is: 200

 

After 2nd states changed:

Top left point's x coordinate: 30

Top left point's y coordinate: 30

The width is: 300

The height is: 300

 

States restored to 1st change:

Top left point's x coordinate: 20

Top left point's y coordinate: 20

The width is: 200

The height is: 200

 

States restored to initial:

Top left point's x coordinate: 10

Top left point's y coordinate: 10

The width is: 100

The height is: 100

 

结果符合预期。

 

补充说明:

1.      未设置其成员变量为public的前提下,在对象的外部保存一个其状态,颇需技巧,而且用不同语言来实现的时候也有所不同。

a.      对于C++,通常使用友元类来实现。

b.      对于C#,使用internal关键字。

c.       对于Java,使用package protected访问控制。

与其处于相同包中的子类,和处于相同包中的其它类均可以访问pakcage protected的对象或变量。

Java中的访问权限有public,private,protected和默认的包访问权限,如果类中的属性方法没有显示的指明访问权

限,则具有包访问权限,很多人也称它为friendly访问权限,也有人称为packeged权限,而packagedfriendly

这两个关键字在实际中都是不存在的。在Java中,访问权限修饰符权限从高到低是publicprotectedpackage

protectedprivate

        d.    C++, C#Java均可使用内部类的方式来实现类似的功能,不过对于“将状态存储于对象之外”而言,稍嫌勉强。

2.      关于拷贝构造函数,请看:http://patmusing.blog.163.com/blog/static/1358349602009113061024796/

3.      关于友元类,请看:http://patmusing.blog.163.com/blog/static/1358349602010182331153/

 
转:http://patmusing.blog.163.com/blog/static/13583496020101501825958/