【愚公系列】2021年12月 二十三种设计模式(十八)-备忘录模式(Memento Pattern)

简介: 【愚公系列】2021年12月 二十三种设计模式(十八)-备忘录模式(Memento Pattern)

文章目录

前言

一、备忘录模式(Memento Pattern)

二、使用步骤

角色

示例

总结

优点

缺点

使用场景

前言

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。


提示:以下是本篇文章正文内容,下面案例可供参考


一、备忘录模式(Memento Pattern)

备忘录模式属于行为型模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。


备忘录模式为我们提供了“后悔药”的机制,为我们在需要的时候,可以将对对象的修改撤销甚至重做。


二、使用步骤

角色

1、原发器(Originator)


创建一个备忘录,用以记录当前时刻它的内部状态,在需要时使用备忘录恢复内部状态;


2、备忘录(Memento)


将原发器对象的内部状态存储起来;


3、备忘录管理者(Caretaker)


负责保存好备忘录,不能对备忘录的内容进行操作或检查。


示例

image.png

命名空间MementoPattern中包含Memento备忘录类,Caretaker管理者类,象棋Chessman类。本案例将向大家演示如何撤销或重做对象棋位置的修改操作。本案例使用栈以支持多次撤销,并且重做支持前1次的多次撤销。本案例不支持重新设置棋子位置时所产生的分支。

public partial class Chessman {
    private Point _position;
    private Caretaker _caretaker = null;
    public Point Position {
        get => _position;
        set {
            _position = value;
            _caretaker.Memento.Position = value; Console.WriteLine(
             String.Format(Const.POSITION_INFO, _position.X, _position.Y));
        }
    }
    public Chessman() : this(new Point(0, 0)) {
    }
    public Chessman(Point point) {
        _caretaker = new Caretaker(new Memento());
        Position = point;
    }
}

象棋棋子类Chessman,内部维持棋子的位置,在设置棋子位置时将信息保存到管理者所管理的备忘录中。

public partial class Chessman {
    public Chessman Undo(int step) {
        try {
            Console.WriteLine(Const.ARROW_LEFT);
            Console.WriteLine($"Undo({step})!");
            this._position = _caretaker.Memento.Undo(step);
            Console.WriteLine(
                String.Format(Const.POSITION_INFO, _position.X, _position.Y));
            Console.WriteLine(Const.ARROW_RIGHT);
            return this;
        } catch(Exception ex) {
            Console.WriteLine(ex.Message);
            Console.WriteLine(Const.ARROW_RIGHT);
            return this;
        }
    }
    public Chessman Redo() {
        try {
            Console.WriteLine(Const.ARROW_LEFT);
            Console.WriteLine("Redo()!");
            this._position = _caretaker.Memento.Redo();
            Console.WriteLine(
                String.Format(Const.POSITION_INFO, _position.X, _position.Y));
            Console.WriteLine(Const.ARROW_RIGHT);
            return this;
        } catch(Exception ex) {
            Console.WriteLine(ex.Message);
            Console.WriteLine(Const.ARROW_RIGHT);
            return this;
        }
    }
}

象棋棋子类Chessman的第2部分(partial),支持按步数撤销位置,并且支持重做命令。

public partial class Memento {
    private Point _position;
    public Point Position {
        get => _position;
        set {
            _position = value;
            _history.Push(new RedoInfo { Position = value });
            _redoList.Clear();
        }
    }
    public Memento() {
        _history = new Stack<RedoInfo>();
        _redoList = new Stack<RedoInfo>();
    }
    private Stack<RedoInfo> _history = null;
    private Stack<RedoInfo> _redoList = null;
    public Point Undo(int step) {
        int totalCount = 0;
        List<string> temp = new List<string>();
        foreach(var item in _history) {
            if(string.IsNullOrWhiteSpace(item.GUID)) {
                totalCount++;
            } else {
                if(!temp.Contains(item.GUID)) {
                    totalCount++;
                    temp.Add(item.GUID);
                }
            }
        }
        if(step >= totalCount) {
            throw new InvalidOperationException("Too much steps!");
        }
        var guid = Guid.NewGuid().ToString("B");
        for(int i = 1; i <= step; i++) {
            Undo(guid);
        }
        return _position;
    }
}

备忘录类Memento,内部维持对位置的引用并用2个栈分别管理历史和重做数据。

public partial class Memento {
    private void UndoLoop(string guid) {
        var history = _history.Pop();
        history.GUID = guid;
        _redoList.Push(history);
        _position = _history.Peek().Position;
    }
    private void Undo(string guid) {
        var temp = _history.Peek().GUID;
        if(string.IsNullOrWhiteSpace(temp)) {
            UndoLoop(guid);
        } else {
            while(_history.Peek().GUID == temp) {
                UndoLoop(guid);
            }
        }
    }
    public Point Redo() {
        if(_redoList.Count == 0) {
            throw new InvalidOperationException("You can not redo now!");
        }
        var guid = _redoList.Peek().GUID;
        while(_redoList.Count != 0 && _redoList.Peek().GUID == guid) {
            _history.Push(_redoList.Pop());
            _position = _history.Peek().Position;
        }
        return _position;
    }
}


备忘录类Memento的第2部分(partial),包含了按步数撤销和重做的具体逻辑。

public class Caretaker {
    public Memento Memento { get; set; }
    public Caretaker(Memento memento) {
        Memento = memento;
    }
}

管理者Caretaker类,管理者负责维持备忘录。

public class RedoInfo {
    public Point Position { get; set; }
    public string GUID { get; set; }
}

重做RedoInfo类,按GUID的值判断是否是同一“批次”被撤销的。

public class Const {
    public const string POSITION_INFO = "Current position is ({0},{1})!";
    public const string ARROW_LEFT = "<---------------------------";
    public const string ARROW_RIGHT = "--------------------------->";
}

常量类Const,维护一些本案例中经常用到的字符串。在实际开发过程中不应当有此类,应该将相应的常量放在具体要使用的类中。2017年,阿里发布《阿里巴巴Java开发手册》,其中有一节提到此准则,所有使用面向对象编程语言的开发人员都应当遵从。

public class Program {
    private static Chessman _chessman = null;
    public static void Main(string[] args) {
        _chessman = new Chessman(new Point(1, 10));
        _chessman.Position = new Point(2, 20);
        _chessman.Position = new Point(3, 30);
        _chessman.Position = new Point(4, 40);
        _chessman.Position = new Point(5, 50);
        _chessman.Position = new Point(9, 40);
        _chessman.Undo(1)
                 .Undo(2)
                 .Undo(1)
                 .Redo()
                 .Redo()
                 .Redo()
                 .Redo()
                 .Redo()
                 .Undo(6)
                 .Undo(5)
                 .Undo(4);
        Console.ReadKey();
    }
}

以上是调用方的代码演示,撤销和重做方法经过特殊处理以支持方法链。以下是这个案例的输出结果:

Current position is (1,10)!
Current position is (2,20)!
Current position is (3,30)!
Current position is (4,40)!
Current position is (5,50)!
Current position is (9,40)!
<---------------------------
Undo(1)!
Current position is (5,50)!
--------------------------->
<---------------------------
Undo(2)!
Current position is (3,30)!
--------------------------->
<---------------------------
Undo(1)!
Current position is (2,20)!
--------------------------->
<---------------------------
Redo()!
Current position is (3,30)!
--------------------------->
<---------------------------
Redo()!
Current position is (5,50)!
--------------------------->
<---------------------------
Redo()!
Current position is (9,40)!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Undo(6)!
Too much steps!
--------------------------->
<---------------------------
Undo(5)!
Too much steps!
--------------------------->
<---------------------------
Undo(4)!
Current position is (1,10)!
--------------------------->

总结

优点

1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态;

2、实现了信息的封装,使得用户不需要关心状态的保存细节。


缺点

1、如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。


使用场景

1、需要保存/恢复数据的相关状态场景;

2、提供一个可回滚的操作。


相关文章
|
3月前
|
存储 设计模式 安全
Java设计模式-备忘录模式(23)
Java设计模式-备忘录模式(23)
|
3月前
|
设计模式
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
这篇文章详细解释了工厂模式,包括简单工厂、工厂方法和抽象工厂三种类型。每种模式都通过代码示例展示了其应用场景和实现方法,并比较了它们之间的差异。简单工厂模式通过一个工厂类来创建各种产品;工厂方法模式通过定义一个创建对象的接口,由子类决定实例化哪个类;抽象工厂模式提供一个创建相关或依赖对象家族的接口,而不需要明确指定具体类。
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
|
3月前
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
4月前
|
设计模式 算法
设计模式的基础问题之备忘录模式在软件开发的问题如何解决
设计模式的基础问题之备忘录模式在软件开发的问题如何解决
|
6月前
|
设计模式
备忘录模式-大话设计模式
备忘录模式-大话设计模式
|
5月前
|
设计模式 数据处理
iLogtail设计模式问题之什么是备忘录模式
iLogtail设计模式问题之什么是备忘录模式
|
6月前
|
设计模式
设计模式-05建造者模式(Builder Pattern)
设计模式-05建造者模式(Builder Pattern)
|
6月前
|
设计模式 存储 Java
Java设计模式之备忘录模式详解
Java设计模式之备忘录模式详解
|
7月前
|
设计模式 安全 Java
【设计模式】JAVA Design Patterns——Curiously Recurring Template Pattern(奇异递归模板模式)
该文介绍了一种C++的编程技巧——奇异递归模板模式(CRTP),旨在让派生组件能继承基本组件的特定功能。通过示例展示了如何创建一个`Fighter`接口和`MmaFighter`类,其中`MmaFighter`及其子类如`MmaBantamweightFighter`和`MmaHeavyweightFighter`强制类型安全,确保相同重量级的拳手之间才能进行比赛。这种设计避免了不同重量级拳手间的错误匹配,编译时会报错。CRTP适用于处理类型冲突、参数化类方法和限制方法只对相同类型实例生效的情况。
【设计模式】JAVA Design Patterns——Curiously Recurring Template Pattern(奇异递归模板模式)
|
7月前
|
设计模式 存储 Go
[设计模式 Go实现] 行为型~备忘录模式
[设计模式 Go实现] 行为型~备忘录模式