您的位置:首页 > 其它

设计模式的艺术之道--备忘录模式

2017-12-12 12:04 162 查看

设计模式的艺术之道–备忘录模式

声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐

本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).

本系列全部源码均在文末地址给出。

本系列开始讲解行为型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。

行为型模式(Behavioral Pattern)

关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责

不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分

类行为型模式

使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责

对象行为型模式

使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责

11种常见的行为型模式



备忘录模式–撤销功能的实现

每个人都有过后悔的时候,但人生并无后悔药。但在软件世界,程序员是上帝,我们可以在软件中实现后悔机制的设计模式——备忘录模式,它是软件中的“后悔药”,是软件中的“月光宝盒”。



1.1定义

-备忘录模式 (Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在以后将对象恢复到原先保存的状态。

- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤

- 当前在很多软件所提供的撤销(Undo)操作中就使用了备忘录模式



1.2情景实例

问题描述

- 可悔棋的中国象棋

菜鸟软件公司欲开发一款触摸式中国象棋软件,由于考虑到有些用户是“菜鸟”,经常不小心走错棋;还有些用户因为不习惯使用手指在屏幕上拖动棋子,常常出现操作失误,因此该中国象棋软件要提供“悔棋”功能,用户走错棋或操作失误后可恢复到前一个步骤。



初步思路分析

如何实现“悔棋”功能是Sunny软件公司开发人员需要面对的一个重要问题,“悔棋”就是让系统恢复到某个历史状态,在很多软件中通常称之为“撤销”。

备忘录模式正为解决此类撤销问题而诞生,它为我们的软件提供了“后悔药”,通过使用备忘录模式可以使系统恢复到某一特定的历史状态。

UML类图



实例关键源代码

class Chessman
{
//象棋棋子类:原发器
private string label;
private int x;
private int y;

public string Label
{
get { return label; }
set { label = value; }
}

public int X
{
get { return x; }
set { x = value; }
}

public int Y
{
get { return y; }
set { y = value; }
}

public Chessman(string label, int x, int y)
{
this.label = label;
this.x = x;
this.y = y;
}

//保存状态
internal ChessmanMemento Save()
{
return new ChessmanMemento(this.Label,this.X,this.Y);
}

//恢复状态
internal void Restore(ChessmanMemento memento)
{
this.Label = memento.Label;
this.X = memento.X;
this.Y = memento.Y;
}
}
internal class ChessmanMemento
{
//象棋棋子备忘录类:备忘录
private string label;
private int x;
private int y;

internal string Label
{
get { return label; }
set { label = value; }
}

internal int X
{
get { return x; }
set { x = value; }
}

internal int Y
{
get { return y; }
set { y = value; }
}

internal ChessmanMemento(string label, int x, int y)
{
this.label = label;
this.x = x;
this.y = y;
}
}
public class MementoCaretaker
{
//象棋棋子备忘录管理类:负责人
private ChessmanMemento memento;

internal ChessmanMemento GetMemento()
{
return memento;
}

internal void SetMemento(ChessmanMemento memento)
{
this.memento = memento;
}
}
class Program
{
public static void Display(Chessman chess)
{
Console.WriteLine("棋子{0}的当前位置为:第{1}行第{2}列。",chess.Label, chess.X, chess.Y);
}

static void Main(string[] args)
{
MementoCaretaker mc = new MementoCaretaker();
Chessman chess = new Chessman("车",1,1);
Display(chess);
mc.SetMemento(chess.Save()); //保存状态
chess.Y = 4;
Display(chess);
mc.SetMemento(chess.Save()); //保存状态
Display(chess);
chess.X = 5;
Display(chess);
Console.WriteLine("******悔棋******");
chess.Restore(mc.GetMemento()); //恢复状态
Display(chess);

Console.Read();
}
}


新的需求

菜鸟软件公司开发人员通过使用备忘录模式实现了中国象棋棋子的撤销操作,但是使用上述代码只能实现一次撤销,因为在负责人类中只定义一个备忘录对象来保存状态,后面保存的状态会将前一次保存的状态覆盖,但有时候用户需要撤销多步操作。如何实现多次撤销呢?可以采用集合类型进行存储(List 队列 栈等)

不同的数据结构对应不同的情况。队列 棋局的完整复盘,栈 多次悔棋操作

新的UML类图

改变了备忘录负责人类的关联对象



改进的实例原代码

public class MementoCaretaker
{
//改进的备忘录负责人
//定义一个集合来存储多个备忘录
private ArrayList mementolist = new ArrayList();

internal ChessmanMemento GetMemento(int i)
{
return (ChessmanMemento)mementolist[i];
}

internal void SetMemento(ChessmanMemento memento)
{
mementolist.Add(memento);
}
}
class Program
{
private static int index = -1; //定义一个索引来记录当前状态所在位置
private static MementoCaretaker mc = new MementoCaretaker();

static void Main(string[] args)
{
Chessman chess = new Chessman("车", 1, 1);
Play(chess);
chess.Y = 4;
Play(chess);
chess.X = 5;
Play(chess);
Undo(chess, index);
Undo(chess, index);
Redo(chess, index);
Redo(chess, index);

Console.Read();
}

//下棋
public static void Play(Chessman chess)
{
mc.SetMemento(chess.Save()); //保存备忘录
index ++;
Console.WriteLine("棋子{0}当前位置为:第{1}行第{2}列。",chess.Label, chess.X, chess.Y);
}

//悔棋
public static void Undo(Chessman chess, int i)
{
Console.WriteLine("******悔棋******");
index --;
chess.Restore(mc.GetMemento(i-1)); //撤销到上一个备忘录
Console.WriteLine("棋子{0}当前位置为:第{1}行第{2}列。", chess.Label, chess.X, chess.Y);
}

//撤销悔棋
public static void Redo(Chessman chess, int i)
{
Console.WriteLine("******撤销悔棋******");
index ++;
chess.Restore(mc.GetMemento(i+1)); //恢复到下一个备忘录
Console.WriteLine("棋子{0}当前位置为:第{1}行第{2}列。", chess.Label, chess.X, chess.Y);
}
}


1.3模式分析

动机和意图

-如何让系统恢复到某一特定的历史状态?如何可以实现撤销操作?

一般结构

备忘录模式包含4个角色:

Originator(原发器):一般将需要保存内部状态的类设计为原发器,

Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。

Caretaker(负责人):在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象。

也可以称为是备忘管理者模式 因为通常需要添加一个单独的备忘录管理者。

备忘录模式UML类图



备忘录模式的实现

除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法

如果允许其他类调用SetState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义 。

理想的情况是只允许生成该备忘录的原发器访问备忘录的内部状态。

C#语言实现:

将Memento类与Originator类定义在同一个程序集(Assembly)中来实现封装,使用访问标识符internal来定义Memento类,即保证其在程序集内可见。

程序集简单的说就是你将你的C#项目经过,运行编译之后形成的dll文件和可执行文件中封装成的程序代码集合。

将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据

改进后的优点

提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤

实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动

现存的缺点

资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间

每保存一次对象的状态都需要消耗一定的系统资源

适用场景

(1) 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时能够恢复到先前的状态,实现撤销操作

(2) 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象

举例:最常见的棋局撤销操作 推箱子游戏撤销操作

网页浏览器的回退操作

实例源代码

GitHub地址

百度云地址:链接: https://pan.baidu.com/s/1kViUhj5 密码: amv2
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: