(48)C#设计模式—— 备忘录模式(Memento Pattern)

引言

今天介绍的备忘录模式与命令模式有些相似,不同的是,命令模式保存的是发起人的具体命令(命令对应的是行为),而备忘录模式保存的是发起人的状态(状态对应的数据结构,如属性)。

定义

从字面意思就可以明白,备忘录模式就是对某个类的状态进行保存,等到需要回复的时候,可以从备忘录中进行恢复。生活中这样的例子经常看到,如备忘电话通讯录,备份数据库等。

备忘录模式的具体定义:在不破坏封装的前提下,不会一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。

结构

涉及到的角色有:

  • 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。
  • 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起然需要的状态
  • 管理者角色:负责保存备忘录对象

实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _39MementoPatternDemo
{
    //联系人
    public class ContactPerson
    {
        public string Name { get; set; }
        public string  MobileNum { get; set; }
    }
    //发起人
    public class MobileOwner
    {
        //发起人需要保存的内部状态
        public List<ContactPerson> ContactPersons { get; set; }
        public MobileOwner(List<ContactPerson> persons)
        {
            ContactPersons = persons;
        }
        //创建备忘录,将当前要保存的联系然列表导入备忘录中
        public ContactMemento Create()
        {
            //这里应该传递深拷贝,new List 方式传递的是浅拷贝
            //因为ContactPerson类中都是string类型,所以这里new List方式对ContactPerson对象执行的深拷贝
            //如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
            return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
        }
        //将备忘录中的数据备份导入到联系人列表中
        public void RestoreMemento(ContactMemento memento)
        {
            //下面这种方式是错误的,因为这样传递的是引用
            //则删除一次可以恢复,但恢复之后再删除的话就恢复不了
            //所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
            this.ContactPersons = memento.contactPersonBack;
        }
        public void Show()
        {
            Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count);
            foreach (ContactPerson p in ContactPersons)
            {
                Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum);
            }
        }
    }
    //备忘录
    public class ContactMemento
    {
        //保存发起人的内部状态
        public List<ContactPerson> contactPersonBack;
        public ContactMemento(List<ContactPerson> persons)
        {
            contactPersonBack = persons;
        }
    }
    //管理角色
    public class Caretaker
    {
        public ContactMemento ContactM { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            List<ContactPerson> persons = new List<ContactPerson>()
            {
                new ContactPerson() {Name="Bob",MobileNum="123456" },
                new ContactPerson() {Name="Tom",MobileNum="789456" },
                new ContactPerson() {Name="Jim",MobileNum="987123" }
            };
            MobileOwner mobileowner = new MobileOwner(persons);
            mobileowner.Show();

            //创建备忘录并保存备忘录对象
            Caretaker caretaker = new Caretaker();
            caretaker.ContactM = mobileowner.Create();

            //更改发起人联系人列表
            Console.WriteLine("===========移除最后一个联系人===========");
            mobileowner.ContactPersons.RemoveAt(2);
            mobileowner.Show();
            //恢复到原始状态
            Console.WriteLine("===========恢复联系人列表===========");
            mobileowner.RestoreMemento(caretaker.ContactM);
            mobileowner.Show();
        }
    }
}

上面的代码只保存了一个还原点,即备忘录中只保存了3个联系人的数据,但是,如果想备份多个还原点怎么办呢?保存多个还原点很简单,只需要保存多个备忘录就可以了。具体实现如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace _39MementoPatternDemo
{
    //联系人
    public class ContactPerson
    {
        public string Name { get; set; }
        public string  MobileNum { get; set; }
    }
    //发起人
    public class MobileOwner
    {
        //发起人需要保存的内部状态
        public List<ContactPerson> ContactPersons { get; set; }
        public MobileOwner(List<ContactPerson> persons)
        {
            ContactPersons = persons;
        }
        //创建备忘录,将当前要保存的联系然列表导入备忘录中
        public ContactMemento Create()
        {
            //这里应该传递深拷贝,new List 方式传递的是浅拷贝
            //因为ContactPerson类中都是string类型,所以这里new List方式对ContactPerson对象执行的深拷贝
            //如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
            return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
        }
        //将备忘录中的数据备份导入到联系人列表中
        public void RestoreMemento(ContactMemento memento)
        {
            //下面这种方式是错误的,因为这样传递的是引用
            //则删除一次可以恢复,但恢复之后再删除的话就恢复不了
            //所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
            this.ContactPersons = memento.contactPersonBack;
        }
        public void Show()
        {
            Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count);
            foreach (ContactPerson p in ContactPersons)
            {
                Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum);
            }
        }
    }
    //备忘录
    public class ContactMemento
    {
        //保存发起人的内部状态
        public List<ContactPerson> contactPersonBack;
        public ContactMemento(List<ContactPerson> persons)
        {
            contactPersonBack = persons;
        }
    }
    //管理角色
    public class Caretaker
    {
        //使用多个备忘录来存储多个备份点
        public Dictionary<string,ContactMemento> ContactMementoDic { get; set; }
        public Caretaker()
        {
            ContactMementoDic = new Dictionary<string, ContactMemento>();
        }
      
    }
    class Program
    {
        static void Main(string[] args)
        {
            List<ContactPerson> persons = new List<ContactPerson>()
            {
                new ContactPerson() {Name="Bob",MobileNum="123456" },
                new ContactPerson() {Name="Tom",MobileNum="789456" },
                new ContactPerson() {Name="Jim",MobileNum="987123" }
            };
            MobileOwner mobileowner = new MobileOwner(persons);
            mobileowner.Show();

            //创建备忘录并保存备忘录对象
            Caretaker caretaker = new Caretaker();
            caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileowner.Create());

            //更改发起人联系人列表
            Console.WriteLine("===========移除最后一个联系人===========");
            mobileowner.ContactPersons.RemoveAt(2);
            mobileowner.Show();
            //创建第二个备份
            Thread.Sleep(1000);
            caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileowner.Create());

            //恢复到原始状态
            Console.WriteLine("===========恢复联系人列表===========");
            var KeyCollection = caretaker.ContactMementoDic.Keys;
            int i = 0;
            foreach (string k in KeyCollection)
            {
                i++;
                Console.WriteLine(i +"      Key = {0}",k);
            }
            while(true)
            {
                Console.WriteLine("请输入数字,按窗口退出键退出:");
                int index = -1;
                try
                {
                    index = Int32.Parse(Console.ReadLine());
                }
                catch 
                {
                    Console.WriteLine("输入格式有误:");
                    continue;
                        
                }
                ContactMemento contactMentor = null;
                if(index<KeyCollection.Count && caretaker.ContactMementoDic.TryGetValue(KeyCollection.ElementAt(index),out contactMentor))
                {
                    mobileowner.RestoreMemento(contactMentor);
                    mobileowner.Show();
                }
                else
                {
                    Console.WriteLine("输入的索引大于集合长度");
                }
            }
        }
    }
}

上面代码保存了多个还原点,客户端可以选择要恢复到哪一个版本。

适用场景

  • 如果系统需要提供回滚操作时,适用备忘录模式非常合适。

优缺点

优点

  • 如果是某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据
  • 备份的状态数据保存在发起人角色之外,这样发起然就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理的,符合单一职责原则

缺点

  • 在实际的系统,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重

总结

备忘录模式主要思想是——利用备忘录对象来保存发起人的内部状态,当发起人需要恢复原来的状态时,再从备忘录对象中进行获取,在实际开发过程中也应用到这一点,例如数据库中的事务处理。

你可能感兴趣的:(C#设计模式)