备忘录模式
1、定义与核心思想
(1)定义
- 备忘录模式(Memento Pattern)是一种行为型设计模式,其核心在于在不破坏对象封装性的前提下,捕获对象的内部状态,并在需要时恢复该状态。
(2)应用场景
- 文本编辑器的撤销操作:如Word的Ctrl+Z。
- 游戏存档与加载:如角色生命值、装备状态的保存。
- 配置管理:保存用户界面配置(如窗口大小、主题设置)。
- 事务回滚:数据库操作失败时恢复到事务开始前的状态。
- AI决策回溯:在博弈类游戏中保存AI的决策路径。
(3)核心角色
- Originator(原发器):负责创建备忘录并保存/恢复自身状态。
- Memento(备忘录):存储Originator的内部状态,仅允许原发器访问数据。
- Caretaker(管理者):存储和管理多个备忘录,不操作其内容。
2、C#代码实现
(1)基础实现
- 以下是一个文本编辑器的撤销功能实现,展示备忘录模式的核心流程
- 封装性:Memento的
Content属性仅允许原发器通过构造函数和属性访问。 - 栈结构:使用
Stack<T>实现多级撤销。
public class TextMemento
{public string Content { get; }public TextMemento(string content) => Content = content;
}
public class TextEditor
{private string _content;public TextMemento CreateMemento() => new TextMemento(_content);public void RestoreMemento(TextMemento memento) => _content = memento.Content;public void Write(string text) => _content += text;public string GetContent() => _content;
}
public class HistoryManager
{private Stack<TextMemento> _history = new Stack<TextMemento>();public void Save(TextEditor editor) => _history.Push(editor.CreateMemento());public void Undo(TextEditor editor){if (_history.Count > 0)editor.RestoreMemento(_history.Pop());}
}
var editor = new TextEditor();
var history = new HistoryManager();editor.Write("Hello");
history.Save(editor);
editor.Write(" World");
history.Undo(editor);
(2)复杂场景扩展
- 以下是一个角色状态存档的扩展实现,参考游戏开发场景。
- 多存档支持:通过字典管理多个存档槽位,支持“快速存档/加载”。
- 数据隔离:备忘录仅暴露必要属性,避免外部直接修改角色内部逻辑。
public class GameSaveMemento
{public int Health { get; }public int AttackPower { get; }public GameSaveMemento(int health, int attackPower){Health = health;AttackPower = attackPower;}
}
public class Player
{private int _health;private int _attackPower;public Player(int health, int attackPower){_health = health;_attackPower = attackPower;}public GameSaveMemento Save() => new GameSaveMemento(_health, _attackPower);public void Load(GameSaveMemento save){_health = save.Health;_attackPower = save.AttackPower;}public void TakeDamage(int damage) => _health = Math.Max(0, _health - damage);public void Heal(int amount) => _health += amount;
}
public class SaveManager
{private Dictionary<string, GameSaveMemento> _saves = new Dictionary<string, GameSaveMemento>();public void AddSave(string slot, GameSaveMemento save) => _saves[slot] = save;public GameSaveMemento GetSave(string slot) => _saves.TryGetValue(slot, out var save) ? save : null;
}
3、高级应用与优化
(1)序列化与持久化存储
- 通过C#的序列化接口(如
ISerializable)将备忘录保存到文件或数据库,实现长期状态管理 - 优点:支持跨会话状态恢复(如重启应用后加载存档)。
[Serializable]
public class SerializableMemento : ISerializable
{public string Data { get; private set; }public SerializableMemento(string data) => Data = data;protected SerializableMemento(SerializationInfo info, StreamingContext context){Data = info.GetString("Data");}public void GetObjectData(SerializationInfo info, StreamingContext context){info.AddValue("Data", Data);}
}
(2)线程安全与深拷贝
- 在多线程环境下,需确保备忘录的线程安全:
- 使用锁机制:在
Caretaker中通过lock关键字保护共享资源。 - 深拷贝实现:通过
MemberwiseClone或第三方库(如AutoMapper)复制对象状态,避免引用类型数据污染。
4、优缺点分析
(1)优点
- 封装性:不暴露对象内部细节,符合面向对象设计原则。
- 可扩展性:支持多级撤销、复杂历史记录管理。
(2)缺点
- 内存消耗:频繁保存大对象可能导致内存压力。
- 性能开销:深拷贝或序列化操作可能影响性能
(3)对比其他模
| 模式 | 核心目的 | 适用场景 |
|---|
| 备忘录模式 | 对象状态保存与恢复 | 撤销、存档、事务回滚 |
| 命令模式 | 解耦请求发送者与执行者 | 操作队列、宏命令 |
| 原型模式 | 通过克隆生成新对象 | 复杂对象初始化优化 |
访问者模式
1、定义与核心思想
(1)定义
- 访问者模式(Visitor Pattern)是一种行为型设计模式,其核心目标是将数据结构与作用于数据结构的操作解耦,使得在不修改数据结构的前提下,能够动态添加新的操作。
- 例如,在处理文档编辑器中的文本、图片等元素时,新增导出PDF或统计字数的功能无需修改元素类本身。
(2)结构与角色
- Visitor(抽象访问者):定义对所有具体元素的访问接口(如
VisitConcreteElementA、VisitConcreteElementB)。 - ConcreteVisitor(具体访问者):实现抽象访问者的接口,定义具体的操作逻辑(如计算价格、生成报告等)。
- Element(抽象元素):声明
Accept方法接收访问者对象。 - ConcreteElement(具体元素):实现
Accept方法,调用访问者的对应方法。 - ObjectStructure(对象结构):管理元素集合并提供遍历接口。
- UML类图
+----------------+ +----------------+
| IVisitor |<---------| ConcreteVisitor|
+----------------+ +----------------+
| +VisitElementA()| | +VisitElementA()|
| +VisitElementB()| | +VisitElementB()|
+----------------+ +----------------+△ △| |
+----------------+ +----------------+
| IElement | | ConcreteElement|
+----------------+ +----------------+
| +Accept(IVisitor)| | +Accept(IVisitor)|
+----------------+ +----------------+
(3)适用场景
- 数据结构稳定但操作多变:如编译器对抽象语法树的分析(类型检查、代码优化等)。
- 跨元素复杂操作:如财务系统中不同账户类型的税务计算。
2、C#代码实现
(1)场景描述
- 假设医院处方单包含多种药品(如止痛药、抗生素),需要支持医生开药与药房发药两种操作。
- 通过访问者模式,新增操作(如药品库存检查)时无需修改药品类。
(2)定义元素接口与具体元素
public interface IMedicine
{void Accept(IMedicineVisitor visitor);
}
public class Painkiller : IMedicine
{public string Name { get; set; }public decimal Price { get; set; }public void Accept(IMedicineVisitor visitor){visitor.VisitPainkiller(this); }
}
public class Antibiotic : IMedicine
{public string Name { get; set; }public bool RequiresPrescription { get; set; }public void Accept(IMedicineVisitor visitor){visitor.VisitAntibiotic(this);}
}
(3)定义访问者接口与具体访问者
public interface IMedicineVisitor
{void VisitPainkiller(Painkiller painkiller);void VisitAntibiotic(Antibiotic antibiotic);
}
public class DoctorVisitor : IMedicineVisitor
{public void VisitPainkiller(Painkiller painkiller){Console.WriteLine($"医生开止痛药:{painkiller.Name},价格:{painkiller.Price:C}");}public void VisitAntibiotic(Antibiotic antibiotic){if (antibiotic.RequiresPrescription)Console.WriteLine($"医生开抗生素:{antibiotic.Name}(需处方)");elseConsole.WriteLine($"医生开抗生素:{antibiotic.Name}");}
}
public class PharmacyVisitor : IMedicineVisitor
{public void VisitPainkiller(Painkiller painkiller){Console.WriteLine($"药房发放止痛药:{painkiller.Name}");}public void VisitAntibiotic(Antibiotic antibiotic){Console.WriteLine($"药房验证抗生素处方:{antibiotic.Name}");}
}
(4)对象结构与客户端调用
public class Prescription
{private readonly List<IMedicine> _medicines = new List<IMedicine>();public void AddMedicine(IMedicine medicine){_medicines.Add(medicine);}public void ProcessMedicines(IMedicineVisitor visitor){foreach (var medicine in _medicines){medicine.Accept(visitor);}}
}
var prescription = new Prescription();
prescription.AddMedicine(new Painkiller { Name = "布洛芬", Price = 25.0m });
prescription.AddMedicine(new Antibiotic { Name = "阿莫西林", RequiresPrescription = true });var doctor = new DoctorVisitor();
var pharmacy = new PharmacyVisitor();Console.WriteLine("=== 医生处理处方 ===");
prescription.ProcessMedicines(doctor);Console.WriteLine("\n=== 药房处理处方 ===");
prescription.ProcessMedicines(pharmacy);
=== 医生处理处方 ===
医生开止痛药:布洛芬,价格:¥25.00
医生开抗生素:阿莫西林(需处方) === 药房处理处方 ===
药房发放止痛药:布洛芬
药房验证抗生素处方:阿莫西林
(5)双重分派机制
- 访问者模式通过两次动态绑定实现双重分派:
- 元素接受访问者:通过
element.Accept(visitor)调用具体元素的Accept方法。 - 访问者访问元素:在
Accept方法中调用visitor.VisitConcreteElement(this),根据具体元素类型选择正确的访问方法。
(6)最佳实践
- 元素接口设计:通过
Accept方法接收访问者,明确操作入口。 - 访问者职责分离:每个访问者仅关注单一功能(如价格计算、处方验证)。
- 性能优化:避免频繁创建访问者对象,可通过对象池或静态访问者优化。
3、高级扩展
(1)组合模式与访问者模式结合
- 处理树形结构时,可通过组合模式定义统一接口,由访问者遍历所有节点。
public interface IComponent
{void Accept(IVisitor visitor);
}public class Composite : IComponent
{private List<IComponent> _children = new List<IComponent>();public void Add(IComponent component) => _children.Add(component);public void Accept(IVisitor visitor){foreach (var child in _children){child.Accept(visitor);}visitor.VisitComposite(this);}
}
(2)动态访问者
public class DynamicVisitor
{public void Visit(object element){var method = GetType().GetMethod("Visit", new[] { element.GetType() });method?.Invoke(this, new[] { element });}
}
3、优缺点分析
(1)优点
- 开闭原则:新增操作只需添加访问者类,无需修改元素类。
- 逻辑集中管理:相关操作聚合在访问者中,避免代码分散(如药品价格计算与处方验证分离)。
(2)缺点
- 破坏封装性:元素需暴露内部状态(如药品价格、处方要求)给访问者。
- 新增元素类型困难:添加新元素需修改所有访问者接口。
(3)对比其他模式
| 模式 | 核心区别 | 适用场景 |
|---|
| 策略模式 | 针对单一算法替换 | 动态选择算法(如排序策略) |
| 装饰器模式 | 动态添加职责 | 增强对象功能(如日志、缓存) |
| 访问者模式 | 跨元素的多操作扩展 | 稳定数据结构+频繁变动的操作 |
解释器模式
1、定义与核心思想
(1)定义
- 解释器模式(Interpreter Pattern)是一种行为型设计模式,用于定义一种语言的文法表示,并提供一个解释器来解释该语言中的句子。
- 其核心目标是通过面向对象的方式,将复杂的语法规则拆解为可扩展的类结构,从而实现语言表达式的动态解析。
(2)动机
- 在软件开发中,某些领域(如数学表达式、配置文件、规则引擎)存在重复出现的复杂逻辑。传统编程方式需频繁修改代码以适应变化,而解释器模式通过将规则抽象为语法树,提供了一种动态扩展的解决方案。
(3)适用场景
- 数学表达式求值:如解析
a + b * 2,通过组合表达式类实现优先级计算。 - 配置文件解析:自定义格式的配置文件(如JSON、XML的子集),通过语法树动态加载配置。
- 规则引擎:业务规则(如折扣策略、风控条件)的动态解析。
- 脚本语言解释器:简单领域特定语言(DSL)的实现,如游戏AI指令。
(4)实际应用案例
- 正则表达式引擎:将正则式解析为状态机,通过解释器模式匹配字符串。
- SQL解析器:将SQL语句转换为查询计划树。
- Unity脚本系统:使用C#解释器执行游戏逻辑脚本。
2、结构与角色
(1)抽象表达式(AbstractExpression)
- 职责:定义所有表达式的公共接口
Interpret(),声明解释操作的规范。 - 代码示例:
public abstract class Expression {public abstract float Interpret(Dictionary<string, float> context);
}
(2)终结符表达式(TerminalExpression)
- 职责:处理语法中的基本元素(如变量、常量),直接与输入内容匹配。
- 代码示例(变量解析):
public class VariableExpression : Expression {private string _variable;public VariableExpression(string variable) => _variable = variable;public override float Interpret(Dictionary<string, float> context) {return context[_variable];}
}
(3)非终结符表达式(NonterminalExpression)
- 职责:组合多个表达式,实现复合逻辑(如加减乘除)。
- 代码示例(加法操作):
public class AddExpression : Expression {private Expression _left;private Expression _right;public AddExpression(Expression left, Expression right) {_left = left;_right = right;}public override float Interpret(Dictionary<string, float> context) {return _left.Interpret(context) + _right.Interpret(context);}
}
(4)上下文(Context)
- 职责:存储解释器全局信息(如变量值映射)。
- 代码示例:
public class Context {public Dictionary<string, float> Variables { get; } = new Dictionary<string, float>();
}
(5)客户端(Client)
public class Client {public static void Main() {Context context = new Context();context.Variables.Add("a", 10);context.Variables.Add("b", 5);Expression expr = new AddExpression(new VariableExpression("a"), new MultiplyExpression(new VariableExpression("b"), new ConstantExpression(2)));float result = expr.Interpret(context); }
}
(6)扩展与优化建议
- 使用访问者模式优化遍历:将解释逻辑与AST分离,提升扩展性。
- 预编译为中间代码:如生成字节码或表达式树(Expression Tree),提高执行效率。
- 引入缓存机制:避免重复解析相同表达式。
4、关键步骤
(1)步骤1:定义语法规则
- 文法设计:使用BNF(巴科斯范式)描述语言规则。
例如,四则运算的文法:
Expression → Term [+/- Term]*
Term → Factor [*/ Factor]*
Factor → Number | Variable | (Expression)
(2)步骤2:构建抽象语法树
- 解析输入字符串:通过词法分析(Lexer)和语法分析(Parser)生成AST。
示例代码(简化版解析器):
public class Parser {private List<Token> _tokens;private int _current = 0;public Expression Parse(string input) {_tokens = Lexer.Tokenize(input);return ParseExpression();}private Expression ParseExpression() {Expression left = ParseTerm();while (Match(TokenType.Plus, TokenType.Minus)) {Token op = Previous();Expression right = ParseTerm();left = new BinaryExpression(left, op, right);}return left;}
}
(3)步骤3:实现解释器逻辑
- 递归解释:通过深度优先遍历AST,逐节点执行计算。
5、布尔表达式解释器案例
- 场景:解析
(A & B) | C形式的布尔表达式。
(1)抽象表达式与终结符
public interface IExpression {bool Interpret(Dictionary<string, bool> context);
}public class VariableExpression : IExpression {private string _name;public VariableExpression(string name) => _name = name;public bool Interpret(Dictionary<string, bool> context) {return context[_name];}
}
(2)非终结符表达式(逻辑运算符)
public class AndExpression : IExpression {private IExpression _left;private IExpression _right;public AndExpression(IExpression left, IExpression right) {_left = left;_right = right;}public bool Interpret(Dictionary<string, bool> context) {return _left.Interpret(context) && _right.Interpret(context);}
}public class OrExpression : IExpression {
}
(3)客户端调用
public class Client {public static void Main() {var context = new Dictionary<string, bool> {{"A", true}, {"B", false}, {"C", true}};IExpression expr = new OrExpression(new AndExpression(new VariableExpression("A"),new VariableExpression("B")),new VariableExpression("C"));bool result = expr.Interpret(context); }
}
6、优缺点分析
(1)优点
- 灵活扩展:新增语法规则只需添加新表达式类。
- 易于维护:通过类结构清晰表达复杂文法。
- 动态性:支持运行时修改语法树(如插件化规则)。
(2)缺点
- 类膨胀:复杂文法导致大量细粒度类,增加维护成本。
- 性能瓶颈:递归遍历AST效率较低,需优化(如缓存中间结果)。
- 适用局限:适合简单文法,复杂语言建议使用编译器生成工具(如ANTLR)。
(3)对比其他模式
| 模式 | 区别 |
|---|
| 访问者模式 | 解耦数据结构与操作,适合在AST上添加新操作(如代码格式化)。 |
| 组合模式 | 解释器模式常利用组合模式构建树形结构。 |
| 策略模式 | 解释器可被视为策略的具体实现,但更关注语法解析。 |