备忘录模式-优雅地备份还原

摘要:备忘录模式我们常见的应用有编辑器ctrl+z可撤销操作,浏览器返回上一次浏览网址,文章撤销编辑等,他们有个共通点,在于备份还原,专业的术语描述备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态或者数据,备份起来,可以随时还原。设计模式Github源码

要实现备忘录模式,总结有三个类:

Model(源数据):是一个普通类/数据实体,你需要对这个类的数据/状态进行备份,为了不破坏这个类的封装性,备份的操作以及存储外置

BackUpModel(备份类):用于备份Model,相当于Model的镜像类

Caretaker(负责人):它是用于执行备份与还原的,它存储着Model的备份版本BackUpModel的数据

引用关系:

Model : BackUpModel

Caretaker : BackUpModel

至于为什么要弄一个BackUpModel作为备份类, 用Caretaker去管理备份类这么麻烦呢?直接在Model里加一个自己对象作为属性来备份不可吗(刚开始编程时候就喜欢啥都堆一起贼方便)?

这个就涉及到程序的设计理念,我们想象一个需求,做一个文章编辑的,一开始我们的需求是可以在草稿箱编辑文章,点发布才正式发布,那文章是实体,我们把文章的草稿当成文章的属性,发布时候只需要重备份实体属性取出来覆盖便可,这样做一开始是没有问题的,但到后来需求变更了,需要把备份变为多个,可以记录多个操作的记录,这时候我们是不是要把原来的草稿属性变为List,这样做就违反了:

开闭原则

开闭原则的英文全称是Open Close Principle缩写即OCP。开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。在软件的生命周期内,因为变化、升级和维护等原因需要对软件的原有代码进行修改时,可能会将错误的代码引入,从而破坏原有系统。因此当软件需求发生变化时,我们应该尽量通过扩展的方式 来实现变化,而不是通过修改已有的代码。

如何不破坏封装性而优雅的实现备份还原功能呢?

便是通过结构备份还原操作,新建个BackUpModel实体,原有的Model只需要一个RestoreMemento方法,属性就是自己的备份实体类,以后一切关于备份还原功能变更都不需要对Model修改了,只要修改Caretaker类便可。

程序实现

c#版本的文章编辑回滚操作

using System;

namespace MementoPattern
{
    /// <summary>
    /// 文章备份实体(某个时刻文章数据状态)
    /// </summary>
    public class ArticleBackup
    {
        public string Title { get; set; }
        public string Content { get; set; }

        public ArticleBackup(string title, string content)
        {
            Title = title;
            Content = content;
        }
    }

    /// <summary>
    /// 文章操作管理者
    /// </summary>
    public class ArticleCaretaker
    {
        public ArticleBackup ArticleBackup { get; set; }
    }

    /// <summary>
    /// 文章实体
    /// </summary>
    public class Article
    {
        public string Title { get; set; }
        public string Content { get; set; }

        public Article(string title, string content)
        {
            Title = title;
            Content = content;
        }

        public bool TryUpdate(string title, string content)
        {
            //code...
            Title = title;
            Content = content;
            return true;
        }

        public ArticleBackup Backup()
        {
            return new ArticleBackup(Title, Content);
        }

        public void RestoreMemento(ArticleBackup model)
        {
            Title = model.Title;
            Content = model.Content;
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            var article = new Article("title", "content");

            //备份
            var messageModelCaretaker = new ArticleCaretaker
            {
                ArticleBackup = article.Backup()
            };

            //更新数据
            article.TryUpdate("new title", "new content");

            Console.WriteLine($"{article.Title},{article.Content}");

            //回滚数据
            article.RestoreMemento(messageModelCaretaker.ArticleBackup);

            Console.WriteLine($"{article.Title},{article.Content}");

            Console.ReadKey();
        }
    }
}

c#版本的文章的版本功能

using System;
using System.Collections.Generic;

namespace MementoPatternExt
{
    /// <summary>
    /// 文章备份实体(某个时刻文章数据状态)
    /// </summary>
    public class ArticleBackup
    {
        public string Title { get; set; }
        public string Content { get; set; }

        public ArticleBackup(string title, string content)
        {
            Title = title;
            Content = content;
        }
    }

    /// <summary>
    /// 文章操作管理者
    /// </summary>
    public class ArticleCaretaker
    {
        /// <summary>
        /// 当前版本号
        /// </summary>
        public int CurrentBatch { get; set; }
        public List<ArticleBackup> ArticleBackupList { get; set; } = new List<ArticleBackup>();

        /// <summary>
        /// 获取上一个版本
        /// </summary>
        /// <returns></returns>
        public ArticleBackup GetPre()
        {
            if (CurrentBatch > 1)
            {
                return ArticleBackupList[CurrentBatch - 2];
            }
            else
            {
                throw new ArgumentException();
            }
        }

        /// <summary>
        /// 获取指定版本
        /// </summary>
        /// <param name="batch"></param>
        /// <returns></returns>
        public ArticleBackup GetByBatch(int batch)
        {
            return ArticleBackupList[batch - 1];
        }

        /// <summary>
        /// 备份新版本
        /// </summary>
        /// <param name="articleBackup"></param>
        public void Set(ArticleBackup articleBackup)
        {
            ArticleBackupList.Add(articleBackup);
            CurrentBatch++;
        }
    }

    /// <summary>
    /// 文章实体
    /// </summary>
    public class Article
    {
        public string Title { get; set; }
        public string Content { get; set; }

        public Article(string title, string content)
        {
            Title = title;
            Content = content;
        }

        public bool TryUpdate(string title, string content)
        {
            //code...
            Title = title;
            Content = content;
            return true;
        }

        public ArticleBackup Backup()
        {
            return new ArticleBackup(Title, Content);
        }

        public void RestoreMemento(ArticleBackup model)
        {
            Title = model.Title;
            Content = model.Content;
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            //版本1
            var article = new Article("title1", "content1");

            var messageModelCaretaker = new ArticleCaretaker();
            messageModelCaretaker.Set(article.Backup());

            article.TryUpdate("title2", "new content2");
            messageModelCaretaker.Set(article.Backup());
            article.TryUpdate("title3", "new content3");
            messageModelCaretaker.Set(article.Backup());
            article.TryUpdate("title4", "new content4");
            messageModelCaretaker.Set(article.Backup());

            Console.WriteLine($"{article.Title},{article.Content}");

            //回滚上一个版本数据
            article.RestoreMemento(messageModelCaretaker.GetPre());
            Console.WriteLine($"获取上一个版本数据{article.Title},{article.Content}");

            article.RestoreMemento(messageModelCaretaker.GetByBatch(1));
            Console.WriteLine($"获取首个版本数据{article.Title},{article.Content}");

            Console.ReadKey();
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容