备忘录模式
1、 我会从有缺陷的代码,修改,并且利用备忘录模式解决,最后会说明在什么场合下使用,使用有什么好处,该模式有哪些缺点。
2、次模式本人由于系统原因没有绘制UML图;
3、此次还讲了备忘录模式的扩展,什么时候用到扩展模式;
4、 这个例子根据生活中吃饭来说
namespace ConsoleApplication.MementoNew
{
/// <summary>
/// 人类
/// 要吃饭
/// </summary>
public class Person
{
private string _Name;
/// <summary>
/// 姓名
/// </summary>
public string Name
{
get { return _Name; }
set { _Name = value; }
}
private string _Bellystate;
/// <summary>
/// 肚子状态
/// </summary>
public string Bellystate
{
get { return _Bellystate; }
set { _Bellystate = value; }
}
}
}
/*
* 客户端
* 我为了好描述 所以在方法内部用了文档注释,在项目中不建议这么做(当也是绝对不用);
* 有缺陷的代码
* 我有的方式,就是在看的时候自己想想如果是我应该怎么做,然后在看答案;
*/
///创建一个小强 不是电影里面的小强
Person person = new Person()
{
Name = "小强",
Bellystate = "不饿"
};
Console.WriteLine("--------{0},现在肚子的情况:{1}", person.Name, person.Bellystate);
//创建一个备份的小强
Person backup = person;
Console.WriteLine("他让人追了一上午");
//追了就肚子饿了
person.Bellystate = "我好饿,跑不动了!";
Console.WriteLine("----------{0}现在肚子的情况:{1}", person.Name, person.Bellystate);
//恢复刚才的备份
//辛亏他遇到我了 我给他吃饺子 填肚子
person.Bellystate = backup.Bellystate;
//Console.WriteLine("-------------{0}现在吃了饺子,肚子的状态:{1}",person.Name,person.Bellystate);
//设计评审:
//上面的需求与业务 相信大家一看就懂
//那我直接分析上面代码 利用OOA OOD GRASP
//上面代码可以运行正常,当不是我想要的,我们看下高层模块(创建小强的代码)
//注意看backup变量的使用,对于高层模块它完全是个多余,(为什么一个状态的保存与备份要高层模块来负责,这应该是person类的责任)
//知道的GRASP的童鞋,一眼就看出来他没有分清楚信息专家,信息专家强调的是把职责分配给具有完成该职责所需要的信息类。
//在用我们常用的的低耦合,来评估这个设计 , 必须要在高层模块保存备份,其实把该职责应该分配给person对象,这样就会低耦合
//在OOD角度说它破坏了面向对象的封装特性,应该把backgroup封装进来,而不让高层模块来完成
方案2 /
/// <summary>
/// 备忘录对象
/// 保存小强肚子的状态
/// </summary>
public class Memento
{
public Memento(string bellystate)
{
this._Bellystate = bellystate;
}
private string _Bellystate;
/// <summary>
/// 肚子状态
/// </summary>
public string Bellystate
{
get { return _Bellystate; }
set { _Bellystate = value; }
}
}
/// <summary>
/// 人类
/// 要吃饭
/// </summary>
public class Person
{
private string _Name;
/// <summary>
/// 姓名
/// </summary>
public string Name
{
get { return _Name; }
set { _Name = value; }
}
private string _Bellystate;
/// <summary>
///
/// </summary>
public string Bellystate
{
get
{
return _Bellystate;
}
set
{
if (string.IsNullOrEmpty(value))
{
return;
}
_Bellystate = value;
}
}
/// <summary>
/// 创建备忘录
/// </summary>
/// <returns></returns>
public Memento CreateMemento()
{
if (string.IsNullOrEmpty(this._Bellystate))
{
return new Memento("......");
}
return new Memento(this._Bellystate);
}
/// <summary>
/// 恢复一个备忘录对象
/// </summary>
/// <param name="memento"></param>
public void RestoreMemento(Memento memento)
{
if (memento == null || string.IsNullOrEmpty(memento.Bellystate))
{
return;
}
this._Bellystate = memento.Bellystate;
}
}
/*
* 改善1:修改以后的代码
* 客户端
*/
Person person1 = new Person()
{
Name = "小强",
Bellystate = "不饿"
};
Console.WriteLine("--------{0},现在肚子的情况:{1}", person1.Name, person1.Bellystate);
//创建一个备份的小强
newMemento.Memento men = person1.CreateMemento();
Console.WriteLine("他让人追了一上午");
//追了就肚子饿了
person1.Bellystate = "我好饿,跑不动了!";
Console.WriteLine("----------{0}现在肚子的情况:{1}", person1.Name, person1.Bellystate);
//恢复刚才的备份
//辛亏他遇到我了 我给他吃饺子 填肚子
person1.RestoreMemento(men);
Console.WriteLine("-------------{0}现在吃了饺子,肚子的状态:{1}", person1.Name, person1.Bellystate);
//设计评审:
//这次把保存与恢复备忘录 放到了memento对象;然后把它封装在person内部,解决了封装的问题,也满足了信息专家模式
//当是还存着问题,1、高层模块需要知道有这么一个备忘录的对象, 这样与高内聚模式有冲突;高内聚讲究的是把不同操作委派给不同对象进行;
//而不是直接在高层模块进行调用(其实与职责分配有关系);
//2、违反了迪米特法则,只和直接的朋友进行联系,高层模块没必要知道备忘录这个对象,知道有个备份点,进行备份就行了;
//大家注意没有时候GRASP、OOD、OOA、OOP\有很多的共同点,因为他们都是为了去解决相同的问题,所以他们出发点都是一样;
/方案3/
/// <summary>
/// 操作备忘录对象
/// 备份点
/// </summary>
public class Caretake
{
/// <summary>
/// 构造函数注入备忘录对象
/// </summary>
public Caretake(Memento memento)
{
if (memento == null)
{
return;
}
this._memento = memento;
}
private Memento _memento;
/// <summary>
/// 备忘录
/// </summary>
public Memento Memento
{
get { return _memento; }
set { _memento = value; }
}
}
Person person2 = new Person()
{
Name = "小强",
Bellystate = "不饿"
};
//创建备份点
newMemento.Caretaker caretaker = new newMemento.Caretaker();
Console.WriteLine("--------{0},现在肚子的情况:{1}", person2.Name, person1.Bellystate);
//创建一个备份的小强 有点鬼 大家可以自己简化
caretaker.Memento = person2.CreateMemento();
Console.WriteLine("他让人追了一上午");
//追了就肚子饿了
person2.Bellystate = "我好饿,跑不动了!";
Console.WriteLine("----------{0}现在肚子的情况:{1}", person2.Name, person1.Bellystate);
//恢复刚才的备份
//辛亏他遇到我了 我给他吃饺子 填肚子
person2.RestoreMemento(caretaker.Memento);
Console.WriteLine("-------------{0}现在吃了饺子,肚子的状态:{1}", person2.Name, person1.Bellystate);
//设计评审:
//就修理了几句代码,大家注意看程序的逻辑比上次要清晰了,需要备份的时候就创建一个备份,然后丢给备份管理者去管理,需要的时候就拿回来
//这个备份就是一个仓库保管员; 这就是备忘录模式.下面会讲点备忘录模式的定义,继续看
//这个备忘录实现的很简单其实真实的项目中有很多情况这个完全不够用怎么办,我会深入讲解备忘录
//这只是个开始,呵呵
备忘录定义
备忘录模式(Memento Pattern)提供一种弥补真实实际缺陷的方法,让小强可以只花一顿饭钱就吃饱,真实事件是不行的;GOF4中这样定义它:在不破坏封装性的前提下,捕获一个对象
内部状态,并且保存该对象,这样以后就看一将该对象状态恢复到以前的状态状态;
通俗的说备忘录模式就是一个备份模式,提供一个备份数据方法 (它的使用场景后面会写出来);
类图中的3个角色说明:(鉴于GRASP中角色)
Originator 发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据;
Memento 备忘路角色
负责存储Originator发起人对象的内部对象状态,在需要的时候提供发起人需要的内部状态;
Creataker 备忘录管理员角色
对备忘录进行管理,保存和提过备忘录
//备忘录模式就这么简单,真正项目中使用就复杂 /// /
备忘录模式的应用
由于备忘录模式有太多的变形和处理方式,每种方式都有自己的优点和缺点,标准的备忘录模式在项目中遇到,基本上都有一些变换的处理方式,因此,我们在使用备忘录模式时候
应该了解如何应用以及注意事项就可以了;
备忘录模式的使用场景
1、需要保存和恢复数据的相关状态场景;
2、需要提供一个可回滚(rollback)的操作,比如VS中的CTRL+Z组合键,IE浏览器中的后退按钮,文件管理器上的BACKSPACE操作;
3、需要监控的副本场景中。例如监控一个对象的属性,当是监控又不应该作为系统的主业物来调用,它只是边缘的应用,即使监控不准,错误报警也影响不大,一般做饭是备份当前的主
线程,然后由分析程序来分析;
4、数据库连接的事物管理器是用备忘录模式,想想在NET中怎么操作事物的吧!开启?回滚?提交?
备忘录模式注意事项:
1、备忘录的生命周期(这里要考虑GRASP中的创建者模式不能把直接使用声明周期的实例,应该在简单工厂或抽象工厂,或你自己写的对象管理什么的,这点NET比JAVA要智能,GCNET的
兄弟都清楚,呵呵);
备忘录创建出来就要在“最近”的代码中使用。要主动管理它的生命周期,建立就要使用,不使用就要立刻删除引用、等待NET的GC对它回收处理;
2、备忘录的性能
不要在频繁建立备忘录场景中使用备忘录模式,(比如FOR 中),会导致控制不了备忘录建立的对象数量,与许啊哟消耗的资源,系统性能要考虑;
备忘录模式的扩展
1、clone方式的备忘录(NET中对象可以直接是用它(你也写一个与JAVA)比JAVA要方便)
回想下原型模式,我们可以通过复制的方式产生一个对象内部的状态,这是一个很好的方法,发起人角色只要Cloneable就成,比较简单,我们看类图;
从类图上,发起人角色融合了发起人角色和备忘录角色,具有双重功能,代码如下:
/// <summary>
/// 直接管理Person 小强
/// 合并了备忘录角色
/// </summary>
public class MementoManager
{
private Person _person;
public Person Person
{
get { return _person; }
set { _person = value; }
}
}
/// <summary>
/// 人类
/// 要吃饭
/// 现在它是备忘录角色与管理员角色进行合并了
/// 它只试用比较简单的场景,尽量不要与其它的对象产生严重的耦合关系,例如:MementoManager又参与其他对象的计算;
/// </summary>
public class Person
{
//与管理类组成聚合的关系 减轻了客户端与实际业务的耦合;
private MementoManager _mementonMan = new MementoManager();
private string _Name;
/// <summary>
/// 姓名
/// </summary>
public string Name
{
get { return _Name; }
set { _Name = value; }
}
private string _Bellystate;
/// <summary>
///
/// </summary>
public string Bellystate
{
get
{
return _Bellystate;
}
set
{
if (string.IsNullOrEmpty(value))
{
return;
}
_Bellystate = value;
}
}
/// <summary>
/// MemberwiseClone 它就是JAVA中的Clone方法
/// 如果你想跟JAVA一样,你就自己写个接口
/// 当然它里面的实现与JAVA是一致的 而且它比JAVA更好控制对象的管理,例如:NET中GC
/// </summary>
public void CreateMento()
{
this._mementonMan.Person = (Person)this.MemberwiseClone();
}
/// <summary>
/// 恢复一个备忘录对象
/// 直接恢复肚子的状态
/// </summary>
/// <param name="memento"></param>
public void RestoreMemento()
{
if (this._mementonMan.Person == null)
{
return;
}
this._Bellystate = this._mementonMan.Person.Bellystate;
}
}
///调用
///Object.MemberwiseClone方式的备忘录
Person person2 = new Person()
{
Name = "小强",
Bellystate = "不饿"
};
Console.WriteLine("--------{0},现在肚子的情况:{1}", person2.Name, person2.Bellystate);
//创建一个备份的小强 有点鬼 大家可以自己简化
person2.CreateMento();
Console.WriteLine("他让人追了一上午");
//追了就肚子饿了
person2.Bellystate = "我好饿,跑不动了!";
Console.WriteLine("----------{0}现在肚子的情况:{1}", person2.Name, person2.Bellystate);
//恢复刚才的备份
//辛亏他遇到我了 我给他吃饺子 填肚子
person2.RestoreMemento();
Console.WriteLine("-------------{0}现在吃了饺子,肚子的状态:{1}", person2.Name, person2.Bellystate);
//设计评审:
//结果是:减少了高层模块与低层模块的依赖,
//不过我们要考虑深拷贝与浅拷贝的问题,在复杂的场景下会让你逻辑混乱,出现错误难跟踪;
//因此它只试用比较简单的场景,不过通过优化还是可以用于复杂的场景
//下面我会讲怎么解决上面的出现的问题,看好了 ,(plase do not copy ,look on it:wangwei 2011/10/09)
//完善备忘录模式 --------多状态备忘录模式
//