凡科建站的优势,电子商务网站的建设流程,泰安电视台新闻综合频道,新西兰网站后缀行为型设计模式之备忘录模式 文章目录 行为型设计模式之备忘录模式模式定义核心思想 核心概念三个核心角色 模式结构工作流程图 实现示例示例一#xff1a;文本编辑器的撤销/重做功能示例二#xff1a;游戏存档系统运行示例 状态演变图应用场景适用场景分析具体应用领域 优缺…行为型设计模式之备忘录模式 文章目录 行为型设计模式之备忘录模式模式定义核心思想 核心概念三个核心角色 模式结构工作流程图 实现示例示例一文本编辑器的撤销/重做功能示例二游戏存档系统运行示例 状态演变图应用场景适用场景分析具体应用领域 优缺点分析优点对比表缺点对比表与其他模式对比 最佳实践设计原则性能优化策略常见陷阱 在线资源 模式定义
备忘录模式Memento Pattern是一种行为型设计模式它在不破坏封装性的前提下捕获一个对象的内部状态并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
核心思想
状态保存与恢复安全地保存和恢复对象的内部状态封装性保护不暴露对象的内部实现细节时间旅行支持撤销/重做等操作
核心概念
三个核心角色
备忘录模式涉及三个关键角色
发起人Originator创建备忘录对象需要恢复状态时使用备忘录备忘录Memento存储发起人的内部状态看护者Caretaker管理备忘录但不能操作或检查备忘录的内容
模式结构 #mermaid-svg-kEbm82Qui6eszby5 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-kEbm82Qui6eszby5 .error-icon{fill:#552222;}#mermaid-svg-kEbm82Qui6eszby5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kEbm82Qui6eszby5 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-kEbm82Qui6eszby5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kEbm82Qui6eszby5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kEbm82Qui6eszby5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kEbm82Qui6eszby5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kEbm82Qui6eszby5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kEbm82Qui6eszby5 .marker.cross{stroke:#333333;}#mermaid-svg-kEbm82Qui6eszby5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kEbm82Qui6eszby5 g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-kEbm82Qui6eszby5 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-kEbm82Qui6eszby5 .nodeLabel,#mermaid-svg-kEbm82Qui6eszby5 .edgeLabel{color:#131300;}#mermaid-svg-kEbm82Qui6eszby5 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-kEbm82Qui6eszby5 .label text{fill:#131300;}#mermaid-svg-kEbm82Qui6eszby5 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-kEbm82Qui6eszby5 .classTitle{font-weight:bolder;}#mermaid-svg-kEbm82Qui6eszby5 .node rect,#mermaid-svg-kEbm82Qui6eszby5 .node circle,#mermaid-svg-kEbm82Qui6eszby5 .node ellipse,#mermaid-svg-kEbm82Qui6eszby5 .node polygon,#mermaid-svg-kEbm82Qui6eszby5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kEbm82Qui6eszby5 .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-kEbm82Qui6eszby5 g.clickable{cursor:pointer;}#mermaid-svg-kEbm82Qui6eszby5 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-kEbm82Qui6eszby5 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-kEbm82Qui6eszby5 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-kEbm82Qui6eszby5 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-kEbm82Qui6eszby5 .dashed-line{stroke-dasharray:3;}#mermaid-svg-kEbm82Qui6eszby5 #compositionStart,#mermaid-svg-kEbm82Qui6eszby5 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #compositionEnd,#mermaid-svg-kEbm82Qui6eszby5 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #dependencyStart,#mermaid-svg-kEbm82Qui6eszby5 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #dependencyStart,#mermaid-svg-kEbm82Qui6eszby5 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #extensionStart,#mermaid-svg-kEbm82Qui6eszby5 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #extensionEnd,#mermaid-svg-kEbm82Qui6eszby5 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #aggregationStart,#mermaid-svg-kEbm82Qui6eszby5 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 #aggregationEnd,#mermaid-svg-kEbm82Qui6eszby5 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-kEbm82Qui6eszby5 .edgeTerminals{font-size:11px;}#mermaid-svg-kEbm82Qui6eszby5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Originator state string createMemento() : Memento restoreMemento(memento Memento) : void setState(state string) : void getState() : string Memento state string getState() : string Caretaker mementoList List addMemento(memento Memento) : void getMemento(index int) : Memento removeMemento(index int) : void 工作流程图 #mermaid-svg-5hIwGlu8kslitKHI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-5hIwGlu8kslitKHI .error-icon{fill:#552222;}#mermaid-svg-5hIwGlu8kslitKHI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5hIwGlu8kslitKHI .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-5hIwGlu8kslitKHI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5hIwGlu8kslitKHI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5hIwGlu8kslitKHI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5hIwGlu8kslitKHI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5hIwGlu8kslitKHI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5hIwGlu8kslitKHI .marker.cross{stroke:#333333;}#mermaid-svg-5hIwGlu8kslitKHI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5hIwGlu8kslitKHI .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5hIwGlu8kslitKHI text.actortspan{fill:black;stroke:none;}#mermaid-svg-5hIwGlu8kslitKHI .actor-line{stroke:grey;}#mermaid-svg-5hIwGlu8kslitKHI .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5hIwGlu8kslitKHI .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5hIwGlu8kslitKHI #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5hIwGlu8kslitKHI .sequenceNumber{fill:white;}#mermaid-svg-5hIwGlu8kslitKHI #sequencenumber{fill:#333;}#mermaid-svg-5hIwGlu8kslitKHI #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5hIwGlu8kslitKHI .messageText{fill:#333;stroke:#333;}#mermaid-svg-5hIwGlu8kslitKHI .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5hIwGlu8kslitKHI .labelText,#mermaid-svg-5hIwGlu8kslitKHI .labelTexttspan{fill:black;stroke:none;}#mermaid-svg-5hIwGlu8kslitKHI .loopText,#mermaid-svg-5hIwGlu8kslitKHI .loopTexttspan{fill:black;stroke:none;}#mermaid-svg-5hIwGlu8kslitKHI .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5hIwGlu8kslitKHI .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5hIwGlu8kslitKHI .noteText,#mermaid-svg-5hIwGlu8kslitKHI .noteTexttspan{fill:black;stroke:none;}#mermaid-svg-5hIwGlu8kslitKHI .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5hIwGlu8kslitKHI .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5hIwGlu8kslitKHI .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5hIwGlu8kslitKHI .actorPopupMenu{position:absolute;}#mermaid-svg-5hIwGlu8kslitKHI .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5hIwGlu8kslitKHI .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5hIwGlu8kslitKHI .actor-man circle,#mermaid-svg-5hIwGlu8kslitKHI line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5hIwGlu8kslitKHI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Client Caretaker Originator Memento 设置状态 保存状态 createMemento() new Memento(state) memento memento 存储memento 后续恢复操作 恢复状态 获取memento restoreMemento(memento) getState() state 恢复内部状态 Client Caretaker Originator Memento 实现示例
示例一文本编辑器的撤销/重做功能
using System;
using System.Collections.Generic;
using System.Text;// 备忘录接口 - 标记接口防止外部直接访问
public interface IMemento
{DateTime Timestamp { get; }
}// 文本编辑器备忘录实现
public class TextEditorMemento : IMemento
{public string Content { get; }public int CursorPosition { get; }public DateTime Timestamp { get; }public string Operation { get; }public TextEditorMemento(string content, int cursorPosition, string operation){Content content ?? string.Empty;CursorPosition cursorPosition;Operation operation ?? Unknown;Timestamp DateTime.Now;}public override string ToString(){return $[{Timestamp:HH:mm:ss}] {Operation} - Length: {Content.Length}, Cursor: {CursorPosition};}
}// 发起人 - 文本编辑器
public class TextEditor
{private StringBuilder _content;private int _cursorPosition;public TextEditor(){_content new StringBuilder();_cursorPosition 0;}// 获取当前文本内容public string Content _content.ToString();// 获取当前光标位置public int CursorPosition _cursorPosition;// 在光标位置插入文本public void InsertText(string text){if (string.IsNullOrEmpty(text)) return;_content.Insert(_cursorPosition, text);_cursorPosition text.Length;Console.WriteLine($插入文本: {text} 在位置 {_cursorPosition - text.Length});Console.WriteLine($当前内容: {Content});}// 删除指定长度的文本public void DeleteText(int length){if (length 0 || _cursorPosition length) return;int startPos _cursorPosition - length;string deletedText _content.ToString(startPos, length);_content.Remove(startPos, length);_cursorPosition startPos;Console.WriteLine($删除文本: {deletedText} 从位置 {startPos});Console.WriteLine($当前内容: {Content});}// 移动光标public void MoveCursor(int position){if (position 0 || position _content.Length) return;_cursorPosition position;Console.WriteLine($光标移动到位置: {_cursorPosition});}// 替换文本public void ReplaceText(int startIndex, int length, string newText){if (startIndex 0 || startIndex _content.Length || length 0) return;string oldText _content.ToString(startIndex, Math.Min(length, _content.Length - startIndex));_content.Remove(startIndex, Math.Min(length, _content.Length - startIndex));_content.Insert(startIndex, newText ?? string.Empty);_cursorPosition startIndex (newText?.Length ?? 0);Console.WriteLine($替换文本: {oldText} - {newText} 在位置 {startIndex});Console.WriteLine($当前内容: {Content});}// 创建备忘录 - 保存当前状态public IMemento CreateMemento(string operation){Console.WriteLine($创建备忘录: {operation});return new TextEditorMemento(_content.ToString(), _cursorPosition, operation);}// 恢复备忘录 - 恢复到之前的状态public void RestoreMemento(IMemento memento){if (memento is not TextEditorMemento textMemento){throw new ArgumentException(无效的备忘录类型);}_content.Clear();_content.Append(textMemento.Content);_cursorPosition textMemento.CursorPosition;Console.WriteLine($恢复备忘录: {textMemento.Operation});Console.WriteLine($恢复后内容: {Content}, 光标位置: {_cursorPosition});}// 显示编辑器状态public void ShowStatus(){Console.WriteLine($编辑器状态 - 内容: {Content}, 光标位置: {_cursorPosition}, 长度: {_content.Length});}
}// 看护者 - 历史管理器
public class HistoryManager
{private readonly ListIMemento _history;private int _currentIndex;private readonly int _maxHistorySize;public HistoryManager(int maxHistorySize 50){_history new ListIMemento();_currentIndex -1;_maxHistorySize maxHistorySize;}// 添加备忘录到历史记录public void SaveState(IMemento memento){// 如果当前不在历史记录的末尾移除之后的所有记录if (_currentIndex _history.Count - 1){_history.RemoveRange(_currentIndex 1, _history.Count - _currentIndex - 1);}// 添加新的备忘录_history.Add(memento);_currentIndex;// 如果超过最大历史记录数移除最早的记录if (_history.Count _maxHistorySize){_history.RemoveAt(0);_currentIndex--;}Console.WriteLine($保存状态到历史记录 (位置: {_currentIndex 1}/{_history.Count}));}// 撤销操作 - 返回上一个状态public IMemento Undo(){if (!CanUndo()){Console.WriteLine(无法撤销已达到历史记录开始处);return null;}_currentIndex--;var memento _history[_currentIndex];Console.WriteLine($执行撤销操作 (位置: {_currentIndex 1}/{_history.Count}));return memento;}// 重做操作 - 返回下一个状态public IMemento Redo(){if (!CanRedo()){Console.WriteLine(无法重做已达到历史记录末尾);return null;}_currentIndex;var memento _history[_currentIndex];Console.WriteLine($执行重做操作 (位置: {_currentIndex 1}/{_history.Count}));return memento;}// 检查是否可以撤销public bool CanUndo(){return _currentIndex 0;}// 检查是否可以重做public bool CanRedo(){return _currentIndex _history.Count - 1;}// 获取历史记录信息public void ShowHistory(){Console.WriteLine($\n 历史记录 (当前位置: {_currentIndex 1}/{_history.Count}) );for (int i 0; i _history.Count; i){string indicator i _currentIndex ? - : ;Console.WriteLine(${indicator}{i 1}. {_history[i]});}Console.WriteLine(\n);}// 清空历史记录public void ClearHistory(){_history.Clear();_currentIndex -1;Console.WriteLine(历史记录已清空);}// 获取历史记录统计public (int total, int current, bool canUndo, bool canRedo) GetStats(){return (_history.Count, _currentIndex 1, CanUndo(), CanRedo());}
}示例二游戏存档系统
using System;
using System.Collections.Generic;
using System.Linq;// 游戏状态备忘录
public class GameStateMemento : IMemento
{public int Level { get; }public int Score { get; }public int Lives { get; }public int Health { get; }public Dictionarystring, int Inventory { get; }public (float x, float y) PlayerPosition { get; }public DateTime Timestamp { get; }public string SaveName { get; }public GameStateMemento(int level, int score, int lives, int health, Dictionarystring, int inventory, (float x, float y) position, string saveName){Level level;Score score;Lives lives;Health health;Inventory new Dictionarystring, int(inventory); // 深拷贝PlayerPosition position;Timestamp DateTime.Now;SaveName saveName ?? $存档_{Timestamp:yyyyMMdd_HHmmss};}public override string ToString(){return ${SaveName} - 等级:{Level}, 分数:{Score}, 生命:{Lives}, 血量:{Health};}
}// 发起人 - 游戏引擎
public class GameEngine
{private int _level;private int _score;private int _lives;private int _health;private Dictionarystring, int _inventory;private (float x, float y) _playerPosition;public GameEngine(){// 初始化游戏状态_level 1;_score 0;_lives 3;_health 100;_inventory new Dictionarystring, int();_playerPosition (0f, 0f);}// 属性访问器public int Level _level;public int Score _score;public int Lives _lives;public int Health _health;public Dictionarystring, int Inventory new Dictionarystring, int(_inventory);public (float x, float y) PlayerPosition _playerPosition;// 游戏操作方法public void LevelUp(){_level;_health 100; // 升级时恢复满血Console.WriteLine($恭喜升级当前等级: {_level});}public void AddScore(int points){_score points;Console.WriteLine($获得 {points} 分当前总分: {_score});}public void TakeDamage(int damage){_health Math.Max(0, _health - damage);Console.WriteLine($受到 {damage} 点伤害当前血量: {_health});if (_health 0){LoseLife();}}public void LoseLife(){if (_lives 0){_lives--;_health 100; // 重生时恢复满血Console.WriteLine($失去一条生命剩余生命: {_lives});}if (_lives 0){Console.WriteLine(游戏结束);}}public void AddToInventory(string item, int quantity){if (_inventory.ContainsKey(item)){_inventory[item] quantity;}else{_inventory[item] quantity;}Console.WriteLine($获得物品: {item} x{quantity});}public bool UseItem(string item, int quantity 1){if (_inventory.ContainsKey(item) _inventory[item] quantity){_inventory[item] - quantity;if (_inventory[item] 0){_inventory.Remove(item);}Console.WriteLine($使用物品: {item} x{quantity});return true;}Console.WriteLine($物品不足: {item});return false;}public void MovePlayer(float x, float y){_playerPosition (x, y);Console.WriteLine($玩家移动到位置: ({x}, {y}));}// 创建游戏存档public IMemento CreateSave(string saveName null){Console.WriteLine($创建游戏存档: {saveName ?? 自动存档});return new GameStateMemento(_level, _score, _lives, _health, _inventory, _playerPosition, saveName);}// 加载游戏存档public void LoadSave(IMemento memento){if (memento is not GameStateMemento gameMemento){throw new ArgumentException(无效的游戏存档);}_level gameMemento.Level;_score gameMemento.Score;_lives gameMemento.Lives;_health gameMemento.Health;_inventory new Dictionarystring, int(gameMemento.Inventory); // 深拷贝_playerPosition gameMemento.PlayerPosition;Console.WriteLine($加载游戏存档: {gameMemento.SaveName});ShowGameState();}// 显示游戏状态public void ShowGameState(){Console.WriteLine(\n 当前游戏状态 );Console.WriteLine($等级: {_level});Console.WriteLine($分数: {_score});Console.WriteLine($生命: {_lives});Console.WriteLine($血量: {_health});Console.WriteLine($位置: ({_playerPosition.x}, {_playerPosition.y}));if (_inventory.Any()){Console.WriteLine(背包物品:);foreach (var item in _inventory){Console.WriteLine($ {item.Key}: {item.Value});}}else{Console.WriteLine(背包为空);}Console.WriteLine(\n);}
}// 看护者 - 存档管理器
public class SaveManager
{private readonly Dictionarystring, IMemento _saveSlots;private readonly ListIMemento _autoSaves;private readonly int _maxAutoSaves;public SaveManager(int maxAutoSaves 10){_saveSlots new Dictionarystring, IMemento();_autoSaves new ListIMemento();_maxAutoSaves maxAutoSaves;}// 保存到指定插槽public void SaveToSlot(string slotName, IMemento memento){_saveSlots[slotName] memento;Console.WriteLine($游戏已保存到插槽: {slotName});}// 从指定插槽加载public IMemento LoadFromSlot(string slotName){if (_saveSlots.TryGetValue(slotName, out var memento)){Console.WriteLine($从插槽加载游戏: {slotName});return memento;}Console.WriteLine($插槽 {slotName} 不存在);return null;}// 自动保存public void AutoSave(IMemento memento){_autoSaves.Add(memento);// 保持自动存档数量限制if (_autoSaves.Count _maxAutoSaves){_autoSaves.RemoveAt(0);}Console.WriteLine($自动保存完成 (自动存档数: {_autoSaves.Count}));}// 获取最新的自动存档public IMemento GetLatestAutoSave(){if (_autoSaves.Count 0){Console.WriteLine(没有自动存档);return null;}var latest _autoSaves.Last();Console.WriteLine(加载最新自动存档);return latest;}// 删除存档插槽public bool DeleteSlot(string slotName){if (_saveSlots.Remove(slotName)){Console.WriteLine($删除存档插槽: {slotName});return true;}Console.WriteLine($插槽 {slotName} 不存在);return false;}// 列出所有存档public void ListSaves(){Console.WriteLine(\n 存档列表 );Console.WriteLine(手动存档:);if (_saveSlots.Any()){foreach (var save in _saveSlots){Console.WriteLine($ [{save.Key}] {save.Value});}}else{Console.WriteLine( 无);}Console.WriteLine($自动存档: {_autoSaves.Count}/{_maxAutoSaves});if (_autoSaves.Any()){for (int i 0; i _autoSaves.Count; i){Console.WriteLine($ [Auto-{i 1}] {_autoSaves[i]});}}Console.WriteLine(\n);}// 获取存档统计信息public (int manualSaves, int autoSaves) GetSaveStats(){return (_saveSlots.Count, _autoSaves.Count);}// 清空所有存档public void ClearAllSaves(){_saveSlots.Clear();_autoSaves.Clear();Console.WriteLine(所有存档已清空);}
}运行示例
class Program
{static void Main(string[] args){Console.WriteLine( 备忘录模式示例一文本编辑器 \n);TextEditorDemo();Console.WriteLine(\n new string(, 60) \n);Console.WriteLine( 备忘录模式示例二游戏存档系统 \n);GameSaveDemo();}static void TextEditorDemo(){var editor new TextEditor();var historyManager new HistoryManager();// 初始状态editor.ShowStatus();historyManager.SaveState(editor.CreateMemento(初始状态));// 执行一系列编辑操作editor.InsertText(Hello);historyManager.SaveState(editor.CreateMemento(插入 Hello));editor.InsertText( World);historyManager.SaveState(editor.CreateMemento(插入 World));editor.InsertText(!);historyManager.SaveState(editor.CreateMemento(插入 !));editor.MoveCursor(5); // 移动到 Hello 和 World 之间editor.InsertText( Beautiful);historyManager.SaveState(editor.CreateMemento(插入 Beautiful));// 显示历史记录historyManager.ShowHistory();// 演示撤销操作Console.WriteLine(\n--- 开始撤销操作 ---);for (int i 0; i 3; i){var memento historyManager.Undo();if (memento ! null){editor.RestoreMemento(memento);editor.ShowStatus();}}// 演示重做操作Console.WriteLine(\n--- 开始重做操作 ---);for (int i 0; i 2; i){var memento historyManager.Redo();if (memento ! null){editor.RestoreMemento(memento);editor.ShowStatus();}}// 在历史记录中间进行新操作Console.WriteLine(\n--- 在历史记录中间进行新操作 ---);editor.ReplaceText(0, 5, Hi);historyManager.SaveState(editor.CreateMemento(替换 Hello 为 Hi));historyManager.ShowHistory();}static void GameSaveDemo(){var game new GameEngine();var saveManager new SaveManager();// 显示初始游戏状态game.ShowGameState();// 玩游戏并创建存档点game.AddScore(100);game.AddToInventory(药水, 3);game.AddToInventory(剑, 1);game.MovePlayer(10.5f, 20.3f);// 手动存档saveManager.SaveToSlot(开始存档, game.CreateSave(第一关开始));saveManager.AutoSave(game.CreateSave(自动存档1));// 继续游戏game.LevelUp();game.AddScore(250);game.TakeDamage(30);game.UseItem(药水, 1);game.AddToInventory(盾牌, 1);game.MovePlayer(25.8f, 45.1f);// 创建更多存档saveManager.SaveToSlot(第二关, game.CreateSave(第二关进度));saveManager.AutoSave(game.CreateSave(自动存档2));// 模拟危险情况game.TakeDamage(70); // 血量归零失去一条生命game.TakeDamage(50);game.AddScore(500);// 当前状态Console.WriteLine(\n--- 当前游戏状态危险中---);game.ShowGameState();// 显示所有存档saveManager.ListSaves();// 加载之前的安全存档Console.WriteLine(\n--- 加载安全存档 ---);var safeState saveManager.LoadFromSlot(第二关);if (safeState ! null){game.LoadSave(safeState);}// 继续游戏Console.WriteLine(\n--- 从安全点继续游戏 ---);game.AddScore(300);game.LevelUp();game.AddToInventory(宝石, 5);// 最终状态game.ShowGameState();// 创建最终存档saveManager.SaveToSlot(最终存档, game.CreateSave(游戏完成));saveManager.ListSaves();}
}状态演变图 #mermaid-svg-qN5yZX9kFwpOKk0E {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E .error-icon{fill:#552222;}#mermaid-svg-qN5yZX9kFwpOKk0E .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qN5yZX9kFwpOKk0E .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-qN5yZX9kFwpOKk0E .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qN5yZX9kFwpOKk0E .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qN5yZX9kFwpOKk0E .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qN5yZX9kFwpOKk0E .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qN5yZX9kFwpOKk0E .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qN5yZX9kFwpOKk0E .marker.cross{stroke:#333333;}#mermaid-svg-qN5yZX9kFwpOKk0E svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qN5yZX9kFwpOKk0E .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E .cluster-label text{fill:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E .cluster-label span{color:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E .label text,#mermaid-svg-qN5yZX9kFwpOKk0E span{fill:#333;color:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E .node rect,#mermaid-svg-qN5yZX9kFwpOKk0E .node circle,#mermaid-svg-qN5yZX9kFwpOKk0E .node ellipse,#mermaid-svg-qN5yZX9kFwpOKk0E .node polygon,#mermaid-svg-qN5yZX9kFwpOKk0E .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qN5yZX9kFwpOKk0E .node .label{text-align:center;}#mermaid-svg-qN5yZX9kFwpOKk0E .node.clickable{cursor:pointer;}#mermaid-svg-qN5yZX9kFwpOKk0E .arrowheadPath{fill:#333333;}#mermaid-svg-qN5yZX9kFwpOKk0E .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qN5yZX9kFwpOKk0E .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qN5yZX9kFwpOKk0E .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-qN5yZX9kFwpOKk0E .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-qN5yZX9kFwpOKk0E .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qN5yZX9kFwpOKk0E .cluster text{fill:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E .cluster span{color:#333;}#mermaid-svg-qN5yZX9kFwpOKk0E div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qN5yZX9kFwpOKk0E :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 看护者管理 撤销 撤销 重做 备忘录1 备忘录2 备忘录3 备忘录4 初始状态 状态1 状态2 状态3 状态4 应用场景
适用场景分析 #mermaid-svg-KbntThbmxy7SVFsp {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-KbntThbmxy7SVFsp .error-icon{fill:#552222;}#mermaid-svg-KbntThbmxy7SVFsp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KbntThbmxy7SVFsp .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-KbntThbmxy7SVFsp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KbntThbmxy7SVFsp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KbntThbmxy7SVFsp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KbntThbmxy7SVFsp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KbntThbmxy7SVFsp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KbntThbmxy7SVFsp .marker.cross{stroke:#333333;}#mermaid-svg-KbntThbmxy7SVFsp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KbntThbmxy7SVFsp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KbntThbmxy7SVFsp .cluster-label text{fill:#333;}#mermaid-svg-KbntThbmxy7SVFsp .cluster-label span{color:#333;}#mermaid-svg-KbntThbmxy7SVFsp .label text,#mermaid-svg-KbntThbmxy7SVFsp span{fill:#333;color:#333;}#mermaid-svg-KbntThbmxy7SVFsp .node rect,#mermaid-svg-KbntThbmxy7SVFsp .node circle,#mermaid-svg-KbntThbmxy7SVFsp .node ellipse,#mermaid-svg-KbntThbmxy7SVFsp .node polygon,#mermaid-svg-KbntThbmxy7SVFsp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KbntThbmxy7SVFsp .node .label{text-align:center;}#mermaid-svg-KbntThbmxy7SVFsp .node.clickable{cursor:pointer;}#mermaid-svg-KbntThbmxy7SVFsp .arrowheadPath{fill:#333333;}#mermaid-svg-KbntThbmxy7SVFsp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KbntThbmxy7SVFsp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KbntThbmxy7SVFsp .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-KbntThbmxy7SVFsp .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-KbntThbmxy7SVFsp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KbntThbmxy7SVFsp .cluster text{fill:#333;}#mermaid-svg-KbntThbmxy7SVFsp .cluster span{color:#333;}#mermaid-svg-KbntThbmxy7SVFsp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KbntThbmxy7SVFsp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 备忘录模式应用场景 撤销/重做功能 状态回滚需求 检查点机制 版本控制系统 文本编辑器 图形设计软件 代码编辑器 数据库事务 游戏存档 配置管理 长时间计算 批处理作业 模拟仿真 Git版本控制 文档版本管理 代码历史记录 具体应用领域 文本编辑器 撤销/重做操作版本历史管理自动保存功能 图形设计软件 操作历史记录状态快照保存非破坏性编辑 游戏开发 存档系统关卡检查点回合制游戏状态 数据库系统 事务回滚快照隔离时点恢复 Web应用 表单状态保存操作历史记录会话管理
优缺点分析
优点对比表 #mermaid-svg-WRoe2qfcXUQ7GcIm {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .error-icon{fill:#552222;}#mermaid-svg-WRoe2qfcXUQ7GcIm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WRoe2qfcXUQ7GcIm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .marker.cross{stroke:#333333;}#mermaid-svg-WRoe2qfcXUQ7GcIm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WRoe2qfcXUQ7GcIm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .cluster-label text{fill:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .cluster-label span{color:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .label text,#mermaid-svg-WRoe2qfcXUQ7GcIm span{fill:#333;color:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .node rect,#mermaid-svg-WRoe2qfcXUQ7GcIm .node circle,#mermaid-svg-WRoe2qfcXUQ7GcIm .node ellipse,#mermaid-svg-WRoe2qfcXUQ7GcIm .node polygon,#mermaid-svg-WRoe2qfcXUQ7GcIm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WRoe2qfcXUQ7GcIm .node .label{text-align:center;}#mermaid-svg-WRoe2qfcXUQ7GcIm .node.clickable{cursor:pointer;}#mermaid-svg-WRoe2qfcXUQ7GcIm .arrowheadPath{fill:#333333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WRoe2qfcXUQ7GcIm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-WRoe2qfcXUQ7GcIm .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-WRoe2qfcXUQ7GcIm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WRoe2qfcXUQ7GcIm .cluster text{fill:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm .cluster span{color:#333;}#mermaid-svg-WRoe2qfcXUQ7GcIm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WRoe2qfcXUQ7GcIm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 备忘录模式优点 封装性保护 简化发起人 灵活的恢复机制 支持多重撤销 不破坏对象封装 隐藏实现细节 发起人职责单一 简化状态管理 任意时间点恢复 选择性状态恢复 历史记录管理 批量操作回滚 缺点对比表 #mermaid-svg-6094uRta5FAijBWV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6094uRta5FAijBWV .error-icon{fill:#552222;}#mermaid-svg-6094uRta5FAijBWV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6094uRta5FAijBWV .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-6094uRta5FAijBWV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6094uRta5FAijBWV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6094uRta5FAijBWV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6094uRta5FAijBWV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6094uRta5FAijBWV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6094uRta5FAijBWV .marker.cross{stroke:#333333;}#mermaid-svg-6094uRta5FAijBWV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6094uRta5FAijBWV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6094uRta5FAijBWV .cluster-label text{fill:#333;}#mermaid-svg-6094uRta5FAijBWV .cluster-label span{color:#333;}#mermaid-svg-6094uRta5FAijBWV .label text,#mermaid-svg-6094uRta5FAijBWV span{fill:#333;color:#333;}#mermaid-svg-6094uRta5FAijBWV .node rect,#mermaid-svg-6094uRta5FAijBWV .node circle,#mermaid-svg-6094uRta5FAijBWV .node ellipse,#mermaid-svg-6094uRta5FAijBWV .node polygon,#mermaid-svg-6094uRta5FAijBWV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6094uRta5FAijBWV .node .label{text-align:center;}#mermaid-svg-6094uRta5FAijBWV .node.clickable{cursor:pointer;}#mermaid-svg-6094uRta5FAijBWV .arrowheadPath{fill:#333333;}#mermaid-svg-6094uRta5FAijBWV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6094uRta5FAijBWV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6094uRta5FAijBWV .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-6094uRta5FAijBWV .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-6094uRta5FAijBWV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6094uRta5FAijBWV .cluster text{fill:#333;}#mermaid-svg-6094uRta5FAijBWV .cluster span{color:#333;}#mermaid-svg-6094uRta5FAijBWV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6094uRta5FAijBWV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 备忘录模式缺点 内存开销 性能影响 复杂性增加 生命周期管理 存储多个状态副本 大对象内存消耗 频繁创建备忘录 深拷贝性能开销 三个角色协调 状态管理复杂 备忘录清理策略 内存泄漏风险 与其他模式对比 #mermaid-svg-aibgDPAKAAgVb5rl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-aibgDPAKAAgVb5rl .error-icon{fill:#552222;}#mermaid-svg-aibgDPAKAAgVb5rl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aibgDPAKAAgVb5rl .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-aibgDPAKAAgVb5rl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aibgDPAKAAgVb5rl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aibgDPAKAAgVb5rl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aibgDPAKAAgVb5rl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aibgDPAKAAgVb5rl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aibgDPAKAAgVb5rl .marker.cross{stroke:#333333;}#mermaid-svg-aibgDPAKAAgVb5rl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aibgDPAKAAgVb5rl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aibgDPAKAAgVb5rl .cluster-label text{fill:#333;}#mermaid-svg-aibgDPAKAAgVb5rl .cluster-label span{color:#333;}#mermaid-svg-aibgDPAKAAgVb5rl .label text,#mermaid-svg-aibgDPAKAAgVb5rl span{fill:#333;color:#333;}#mermaid-svg-aibgDPAKAAgVb5rl .node rect,#mermaid-svg-aibgDPAKAAgVb5rl .node circle,#mermaid-svg-aibgDPAKAAgVb5rl .node ellipse,#mermaid-svg-aibgDPAKAAgVb5rl .node polygon,#mermaid-svg-aibgDPAKAAgVb5rl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aibgDPAKAAgVb5rl .node .label{text-align:center;}#mermaid-svg-aibgDPAKAAgVb5rl .node.clickable{cursor:pointer;}#mermaid-svg-aibgDPAKAAgVb5rl .arrowheadPath{fill:#333333;}#mermaid-svg-aibgDPAKAAgVb5rl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aibgDPAKAAgVb5rl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aibgDPAKAAgVb5rl .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-aibgDPAKAAgVb5rl .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-aibgDPAKAAgVb5rl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aibgDPAKAAgVb5rl .cluster text{fill:#333;}#mermaid-svg-aibgDPAKAAgVb5rl .cluster span{color:#333;}#mermaid-svg-aibgDPAKAAgVb5rl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aibgDPAKAAgVb5rl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 备忘录模式 vs 其他模式 vs 命令模式 vs 原型模式 vs 快照模式 备忘录: 状态恢复 命令: 操作撤销 备忘录: 状态保存 原型: 对象复制 备忘录: 完整状态 快照: 轻量级状态 最佳实践
设计原则 接口隔离 // 推荐使用标记接口
public interface IMemento
{DateTime Timestamp { get; }
}// 具体实现对外不可见
internal class ConcreteMemento : IMemento
{internal string State { get; }public DateTime Timestamp { get; }// ...
}封装保护 public class Originator
{private string _internalState;// 只允许创建者访问内部状态public IMemento CreateMemento(){return new ConcreteMemento(_internalState);}public void RestoreMemento(IMemento memento){if (memento is ConcreteMemento concrete){_internalState concrete.State;}}
}内存管理 public class HistoryManager : IDisposable
{private readonly QueueIMemento _history;private readonly int _maxSize;public void AddMemento(IMemento memento){if (_history.Count _maxSize){_history.Dequeue(); // 移除最早的记录}_history.Enqueue(memento);}public void Dispose(){_history.Clear();}
}性能优化策略
延迟复制只在需要时创建状态副本增量备忘录只保存变化的部分压缩存储对备忘录数据进行压缩异步保存在后台线程创建备忘录
常见陷阱
深拷贝与浅拷贝内存泄漏风险线程安全问题性能过度优化
在线资源
Microsoft C# 设计模式文档备忘录模式详解C# 设计模式实现
文章转载自: http://www.morning.spxk.cn.gov.cn.spxk.cn http://www.morning.ykwgl.cn.gov.cn.ykwgl.cn http://www.morning.rcwzf.cn.gov.cn.rcwzf.cn http://www.morning.mstbbs.com.gov.cn.mstbbs.com http://www.morning.fdhwh.cn.gov.cn.fdhwh.cn http://www.morning.dnmgr.cn.gov.cn.dnmgr.cn http://www.morning.c7497.cn.gov.cn.c7497.cn http://www.morning.ccyns.cn.gov.cn.ccyns.cn http://www.morning.jopebe.cn.gov.cn.jopebe.cn http://www.morning.qgkcs.cn.gov.cn.qgkcs.cn http://www.morning.gcrlb.cn.gov.cn.gcrlb.cn http://www.morning.lyhry.cn.gov.cn.lyhry.cn http://www.morning.msgrq.cn.gov.cn.msgrq.cn http://www.morning.lmqfq.cn.gov.cn.lmqfq.cn http://www.morning.znnsk.cn.gov.cn.znnsk.cn http://www.morning.rcfwr.cn.gov.cn.rcfwr.cn http://www.morning.qmnjn.cn.gov.cn.qmnjn.cn http://www.morning.gctgc.cn.gov.cn.gctgc.cn http://www.morning.ckfqt.cn.gov.cn.ckfqt.cn http://www.morning.bslkt.cn.gov.cn.bslkt.cn http://www.morning.kkjhj.cn.gov.cn.kkjhj.cn http://www.morning.btqqh.cn.gov.cn.btqqh.cn http://www.morning.sdecsd.cn.gov.cn.sdecsd.cn http://www.morning.xbnkm.cn.gov.cn.xbnkm.cn http://www.morning.gjlst.cn.gov.cn.gjlst.cn http://www.morning.kjcll.cn.gov.cn.kjcll.cn http://www.morning.nlywq.cn.gov.cn.nlywq.cn http://www.morning.wmdlp.cn.gov.cn.wmdlp.cn http://www.morning.rdkqt.cn.gov.cn.rdkqt.cn http://www.morning.bflwj.cn.gov.cn.bflwj.cn http://www.morning.rgsnk.cn.gov.cn.rgsnk.cn http://www.morning.wjfzp.cn.gov.cn.wjfzp.cn http://www.morning.jtmql.cn.gov.cn.jtmql.cn http://www.morning.bgqr.cn.gov.cn.bgqr.cn http://www.morning.hgsmz.cn.gov.cn.hgsmz.cn http://www.morning.sggzr.cn.gov.cn.sggzr.cn http://www.morning.knzdt.cn.gov.cn.knzdt.cn http://www.morning.zzqgc.cn.gov.cn.zzqgc.cn http://www.morning.rnzwh.cn.gov.cn.rnzwh.cn http://www.morning.pbwcq.cn.gov.cn.pbwcq.cn http://www.morning.nzcgj.cn.gov.cn.nzcgj.cn http://www.morning.rbrhj.cn.gov.cn.rbrhj.cn http://www.morning.tckxl.cn.gov.cn.tckxl.cn http://www.morning.tpmnq.cn.gov.cn.tpmnq.cn http://www.morning.rpjyl.cn.gov.cn.rpjyl.cn http://www.morning.gydth.cn.gov.cn.gydth.cn http://www.morning.cczzyy.com.gov.cn.cczzyy.com http://www.morning.rqqct.cn.gov.cn.rqqct.cn http://www.morning.tqpnf.cn.gov.cn.tqpnf.cn http://www.morning.stsnf.cn.gov.cn.stsnf.cn http://www.morning.sbncr.cn.gov.cn.sbncr.cn http://www.morning.spwm.cn.gov.cn.spwm.cn http://www.morning.tslwz.cn.gov.cn.tslwz.cn http://www.morning.zylzk.cn.gov.cn.zylzk.cn http://www.morning.qnpyz.cn.gov.cn.qnpyz.cn http://www.morning.crkmm.cn.gov.cn.crkmm.cn http://www.morning.ylxgw.cn.gov.cn.ylxgw.cn http://www.morning.kgjyy.cn.gov.cn.kgjyy.cn http://www.morning.czxrg.cn.gov.cn.czxrg.cn http://www.morning.lmhh.cn.gov.cn.lmhh.cn http://www.morning.cfcdr.cn.gov.cn.cfcdr.cn http://www.morning.qrzwj.cn.gov.cn.qrzwj.cn http://www.morning.jrwbl.cn.gov.cn.jrwbl.cn http://www.morning.rttxx.cn.gov.cn.rttxx.cn http://www.morning.gwwky.cn.gov.cn.gwwky.cn http://www.morning.mhmcr.cn.gov.cn.mhmcr.cn http://www.morning.gwwky.cn.gov.cn.gwwky.cn http://www.morning.xpfwr.cn.gov.cn.xpfwr.cn http://www.morning.yrbp.cn.gov.cn.yrbp.cn http://www.morning.lhrxq.cn.gov.cn.lhrxq.cn http://www.morning.cwrnr.cn.gov.cn.cwrnr.cn http://www.morning.lxhrq.cn.gov.cn.lxhrq.cn http://www.morning.ydxwj.cn.gov.cn.ydxwj.cn http://www.morning.vnuwdy.cn.gov.cn.vnuwdy.cn http://www.morning.lxbml.cn.gov.cn.lxbml.cn http://www.morning.gzzxlp.com.gov.cn.gzzxlp.com http://www.morning.prjns.cn.gov.cn.prjns.cn http://www.morning.dkzwx.cn.gov.cn.dkzwx.cn http://www.morning.lpyjq.cn.gov.cn.lpyjq.cn http://www.morning.smggx.cn.gov.cn.smggx.cn