原型模式
大约 12 分钟约 3727 字
原型模式
简介
原型(Prototype)通过复制已有实例创建新对象,而非通过 new 关键字。理解原型模式,有助于在对象创建成本高或需要保留初始状态的场景中提升性能。
原型模式的灵感来自现实生活中的"克隆"概念 —— 不从零开始构造,而是基于一个已有的"模板"进行复制。在软件中,当对象的创建过程涉及复杂的初始化、数据库查询、网络请求或大量计算时,直接克隆一个已有对象比重新创建要高效得多。
GoF 对原型模式的定义为:"用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。"
特点
结构分析
UML 类图
+------------------+
| ICloneable | <-- .NET 内置接口
+------------------+
| +Clone(): object |
+------------------+
^
|
+-------------+-------------+
| | |
+----------+ +----------+ +-----------+
| Employee | | Shape | | Workflow |
| (具体类) | | (抽象类) | | Config |
+----------+ +----------+ +-----------+
| +Clone() | | +Clone() | | (JSON深拷贝|
+----------+ +----------+ +-----------+
+------------------+
| PrototypeRegistry| <-- 原型注册表
+------------------+
| -_prototypes: Map|
| +Register() |
| +Create(): Shape |
+------------------+浅拷贝 vs 深拷贝
浅拷贝 (MemberwiseClone) 深拷贝
+------------------+ +------------------+
| original | | original |
| -Name: "张三" | | -Name: "张三" |
| -Age: 30 | | -Age: 30 |
| -Address: [ref]--+---共享--->| -Address: [ref]--+---独立---> Address{北京}
+------------------+ +------------------+
|
+------------------+
| clone |
| -Name: "张三" |
| -Age: 30 |
| -Address: [ref]--+---独立---> Address{北京}
+------------------+实现
ICloneable 与深拷贝
// 浅拷贝 vs 深拷贝
public class Address
{
public string City { get; set; } = "";
public string Street { get; set; } = "";
public override string ToString() => $"{City} {Street}";
}
public class Employee : ICloneable
{
public string Name { get; set; } = "";
public int Age { get; set; }
public Address Address { get; set; } = new();
public List<string> Skills { get; set; } = new();
// 浅拷贝 — 引用类型共享
public object Clone() => MemberwiseClone();
// 深拷贝 — 创建所有引用类型的新实例
public Employee DeepClone()
{
var clone = (Employee)MemberwiseClone();
clone.Address = new Address { City = Address.City, Street = Address.Street };
clone.Skills = new List<string>(Skills);
return clone;
}
}
// 浅拷贝问题演示
var original = new Employee { Name = "张三", Age = 30, Address = new Address { City = "北京", Street = "长安街" } };
var shallowCopy = (Employee)original.Clone();
shallowCopy.Address.City = "上海"; // 修改影响 original!
Console.WriteLine(original.Address.City); // "上海" — 被污染
// 深拷贝安全
var deepCopy = original.DeepClone();
deepCopy.Address.City = "广州"; // 不影响 original原型注册表
// 图形对象原型
public abstract class Shape : ICloneable
{
public string Color { get; set; } = "black";
public int X { get; set; }
public int Y { get; set; }
public abstract void Draw();
public abstract object Clone();
}
public class Circle : Shape
{
public int Radius { get; set; }
public override void Draw() => Console.WriteLine($"圆形: 位置({X},{Y}), 半径={Radius}, 颜色={Color}");
public override object Clone() => (Circle)MemberwiseClone();
}
public class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public override void Draw() => Console.WriteLine($"矩形: 位置({X},{Y}), {Width}x{Height}, 颜色={Color}");
public override object Clone() => (Rectangle)MemberwiseClone();
}
// 原型注册表
public class ShapePrototypeRegistry
{
private readonly Dictionary<string, Shape> _prototypes = new();
public void Register(string key, Shape prototype) => _prototypes[key] = prototype;
public Shape Create(string key)
{
if (!_prototypes.TryGetValue(key, out var prototype))
throw new KeyNotFoundException($"未注册的原型: {key}");
return (Shape)prototype.Clone();
}
public T Create<T>(string key) where T : Shape => (T)Create(key);
}
// 预注册标准形状
var registry = new ShapePrototypeRegistry();
registry.Register("red_circle", new Circle { Color = "red", Radius = 10 });
registry.Register("blue_rect", new Rectangle { Color = "blue", Width = 100, Height = 50 });
registry.Register("grid_cell", new Rectangle { Color = "gray", Width = 20, Height = 20 });
// 快速克隆 — 无需重新设置默认值
var circle1 = registry.Create<Circle>("red_circle");
circle1.X = 100; circle1.Y = 200;
var cell1 = registry.Create<Rectangle>("grid_cell");
cell1.X = 0; cell1.Y = 0;序列化实现深拷贝
// 通用深拷贝工具 — 使用 JSON 序列化
public static class DeepCloneHelper
{
public static T JsonClone<T>(T source) where T : class
{
var json = JsonSerializer.Serialize(source);
return JsonSerializer.Deserialize<T>(json)!;
}
}
// 使用 System.Text.Json 实现深拷贝的复杂对象
public class WorkflowConfig
{
public string Name { get; set; } = "";
public List<WorkflowStep> Steps { get; set; } = new();
public Dictionary<string, string> Variables { get; set; } = new();
}
public class WorkflowStep
{
public string Action { get; set; } = "";
public Dictionary<string, object> Parameters { get; set; } = new();
public List<WorkflowStep> SubSteps { get; set; } = new();
}
var template = new WorkflowConfig
{
Name = "数据导入模板",
Steps = new List<WorkflowStep>
{
new() { Action = "Validate", Parameters = { ["strict"] = true } },
new() { Action = "Transform", SubSteps = new List<WorkflowStep>
{
new() { Action = "MapFields" },
new() { Action = "FilterDuplicates" }
}}
}
};
// 从模板深拷贝创建新工作流
var instance = DeepCloneHelper.JsonClone(template);
instance.Name = "数据导入 - 2026-04-12";
instance.Variables["batch_id"] = "B001";实战:配置模板克隆
在企业应用中,许多配置需要基于模板创建。例如,新项目的 CI/CD 流水线、新环境的配置等。
// 通知渠道配置模板
public class NotificationConfig
{
public string Name { get; set; } = "";
public List<string> EmailRecipients { get; set; } = new();
public List<string> WebhookUrls { get; set; } = new();
public Dictionary<string, string> Rules { get; set; } = new();
public TimeSpan RetryInterval { get; set; } = TimeSpan.FromMinutes(5);
public int MaxRetries { get; set; } = 3;
public bool Enabled { get; set; } = true;
}
public class NotificationTemplateManager
{
private readonly Dictionary<string, NotificationConfig> _templates = new();
public void RegisterTemplate(string name, NotificationConfig config)
{
_templates[name] = config;
}
// 从模板克隆并自定义
public NotificationConfig CreateFromTemplate(string templateName, string instanceName)
{
if (!_templates.TryGetValue(templateName, out var template))
throw new KeyNotFoundException($"模板不存在: {templateName}");
var instance = DeepCloneHelper.JsonClone(template);
instance.Name = instanceName;
return instance;
}
}
// 使用
var manager = new NotificationTemplateManager();
// 注册标准模板
manager.RegisterTemplate("critical_alert", new NotificationConfig
{
Name = "关键告警模板",
EmailRecipients = new List<string> { "oncall@example.com", "admin@example.com" },
WebhookUrls = new List<string> { "https://hooks.slack.com/xxx" },
Rules = new Dictionary<string, string>
{
["min_severity"] = "critical",
["cooldown_minutes"] = "30"
},
MaxRetries = 5
});
// 从模板创建具体实例
var dbAlert = manager.CreateFromTemplate("critical_alert", "数据库告警");
dbAlert.Rules["min_severity"] = "database_error";
var apiAlert = manager.CreateFromTemplate("critical_alert", "API 告警");
apiAlert.Rules["min_severity"] = "api_error";
apiAlert.EmailRecipients.Add("api-team@example.com");实战:游戏角色克隆
public class GameCharacter
{
public string Name { get; set; } = "";
public int Health { get; set; } = 100;
public int Attack { get; set; } = 10;
public int Defense { get; set; } = 5;
public List<string> Inventory { get; set; } = new();
public Dictionary<string, int> Skills { get; set; } = new();
public Equipment? Equipment { get; set; }
public GameCharacter Clone()
{
var clone = (GameCharacter)MemberwiseClone();
clone.Inventory = new List<string>(Inventory);
clone.Skills = new Dictionary<string, int>(Skills);
clone.Equipment = Equipment?.Clone();
return clone;
}
}
public class Equipment
{
public string Weapon { get; set; } = "木剑";
public string Armor { get; set; } = "布甲";
public int BonusAttack { get; set; }
public int BonusDefense { get; set; }
public Equipment Clone() => new()
{
Weapon = Weapon,
Armor = Armor,
BonusAttack = BonusAttack,
BonusDefense = BonusDefense
};
}
// 定义角色模板
var warriorTemplate = new GameCharacter
{
Name = "战士模板",
Health = 200,
Attack = 25,
Defense = 15,
Skills = new Dictionary<string, int> { ["冲锋"] = 1, ["旋风斩"] = 3 },
Equipment = new Equipment { Weapon = "铁剑", Armor = "铁甲", BonusAttack = 10, BonusDefense = 5 }
};
// 快速创建新角色
var player1 = warriorTemplate.Clone();
player1.Name = "张三的战士";
var player2 = warriorTemplate.Clone();
player2.Name = "李四的战士";
player2.Skills["冲锋"] = 5; // 不影响 player1深拷贝方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动实现 | 性能最优、完全可控 | 代码量大、维护成本高 | 性能敏感、结构简单 |
| MemberwiseClone | .NET 内置、速度快 | 仅浅拷贝 | 值类型为主的对象 |
| JSON 序列化 | 通用、支持任意嵌套 | 性能较差、需无参构造 | 结构复杂、非性能敏感 |
| Expression Tree | 编译时优化 | 实现复杂 | 高性能深拷贝需求 |
| 二进制序列化 | 支持循环引用 | 安全风险、兼容性差 | 遗留系统 |
与其他创建型模式的对比
原型模式 工厂方法 抽象工厂 建造者
+--------+ +--------+ +----------+ +--------+
| 克隆 | | 子类 | | 产品族 | | 分步 |
| 创建 | | 创建 | | 创建 | | 构建 |
+--------+ +--------+ +----------+ +--------+
| 保留 | | 延迟到 | | 一组相关 | | 复杂 |
| 初始 | | 子类 | | 对象 | | 对象 |
| 状态 | | 决定 | | | | 组装 |
+--------+ +--------+ +----------+ +--------+- 原型 vs 工厂方法:原型通过克隆创建,工厂方法通过继承创建。当创建成本高时用原型,当创建逻辑稳定时用工厂方法。
- 原型 vs 建造者:原型一步完成复制,建造者多步组装。当需要定制创建过程时用建造者,当需要快速复制已有对象时用原型。
C# record 与 with 表达式
现代 C# 的原型替代方案
// C# 9+ 的 record 类型天然支持非破坏性复制
// with 表达式创建副本并修改部分属性
public record Person(string Name, int Age, string[] Tags);
var original = new Person("张三", 30, new[] { "C#", "SQL" });
// with 表达式 — 创建副本并修改 Name
var clone1 = original with { Name = "李四" };
// clone1 = Person { Name = "李四", Age = 30, Tags = ["C#", "SQL"] }
// 多属性修改
var clone2 = original with { Name = "王五", Age = 25 };
// clone2 = Person { Name = "王五", Age = 25, Tags = ["C#", "SQL"] }
// 注意:数组是引用类型,with 不会深拷贝数组
// clone1.Tags 和 original.Tags 引用同一个数组
// 如果需要深拷贝数组,手动处理
var deepClone = original with { Tags = (string[])original.Tags.Clone() };
deepClone.Tags[0] = "Python"; // 不影响 original
// record struct(C# 10)
public record struct Point(double X, double Y);
var p1 = new Point(1.0, 2.0);
var p2 = p1 with { X = 3.0 }; // Point { X = 3.0, Y = 2.0 }record 与原型注册表结合
// 使用 record 的 with 表达式替代传统的 Clone 方法
public record ReportTemplate(
string Name,
string Type,
string[] Sections,
Dictionary<string, string> Parameters,
bool IsEnabled
);
public class ReportTemplateManager
{
private readonly Dictionary<string, ReportTemplate> _templates = new();
public void Register(string key, ReportTemplate template)
=> _templates[key] = template;
public ReportTemplate CreateFrom(string key, Action<ReportTemplateMutator> configure)
{
if (!_templates.TryGetValue(key, out var template))
throw new KeyNotFoundException($"模板不存在: {key}");
var mutator = new ReportTemplateMutator(template);
configure(mutator);
return mutator.Build();
}
}
// 使用 mutator 模式配合 record
public class ReportTemplateMutator
{
private ReportTemplate _template;
public ReportTemplateMutator(ReportTemplate template) => _template = template;
public string Name { set => _template = _template with { Name = value }; }
public string[] Sections { set => _template = _template with { Sections = value }; }
public bool IsEnabled { set => _template = _template with { IsEnabled = value }; }
public ReportTemplate Build() => _template;
}
// 使用示例
var manager = new ReportTemplateManager();
manager.Register("monthly", new ReportTemplate(
Name: "月度报告模板",
Type: "Financial",
Sections: new[] { "摘要", "收入", "支出", "利润" },
Parameters: new Dictionary<string, string> { ["currency"] = "CNY" },
IsEnabled: true
));
var report = manager.CreateFrom("monthly", m =>
{
m.Name = "2026年4月财务报告";
m.Sections = new[] { "摘要", "收入", "利润" };
});深拷贝高级方案
Expression Tree 深拷贝
// 使用 Expression Tree 实现高性能深拷贝
// 性能接近手动实现,远优于反射和 JSON 序列化
public static class ExpressionTreeClone
{
private static readonly ConcurrentDictionary<Type, Delegate> _cache = new();
public static T Clone<T>(T source) where T : class
{
var cloner = (Func<T, T>)_cache.GetOrAdd(typeof(T), t => CreateCloner<T>());
return cloner(source);
}
private static Func<T, T> CreateCloner<T>() where T : class
{
var type = typeof(T);
var param = Expression.Parameter(type, "source");
var bindings = new List<MemberBinding>();
foreach (var prop in type.GetProperties())
{
if (!prop.CanRead || !prop.CanWrite) continue;
var propAccess = Expression.Property(param, prop);
Expression valueExpr = prop.PropertyType.IsValueType
? (Expression)propAccess
: Expression.TypeAs(
Expression.Call(
typeof(object), "MemberwiseClone",
Type.EmptyTypes,
Expression.Convert(propAccess, typeof(object))
),
prop.PropertyType
);
bindings.Add(Expression.Bind(prop, valueExpr));
}
var newExpr = Expression.MemberInit(Expression.New(type), bindings);
return Expression.Lambda<Func<T, T>>(newExpr, param).Compile();
}
}
// 使用 Source Generator 实现(.NET 6+)
// 通过编译时代码生成,避免反射开销
// [GenerateClone]
// public partial class MyEntity { ... }
// 自动生成 Clone() 方法原型模式在游戏开发中的应用
对象池模式
// 对象池模式与原型模式结合 — 复用克隆对象
public class ObjectPool<T> where T : class, ICloneable
{
private readonly ConcurrentBag<T> _pool = new();
private readonly T _prototype;
private readonly int _maxSize;
private int _currentCount;
public ObjectPool(T prototype, int maxSize = 100)
{
_prototype = prototype;
_maxSize = maxSize;
}
public T Rent()
{
if (_pool.TryTake(out var item))
{
Interlocked.Decrement(ref _currentCount);
return item;
}
// 池为空时克隆新对象
return (T)_prototype.Clone();
}
public void Return(T item)
{
if (Interlocked.Increment(ref _currentCount) <= _maxSize)
{
_pool.Add(item);
}
else
{
Interlocked.Decrement(ref _currentCount);
}
}
}
// 使用对象池管理子弹对象
public class Bullet : ICloneable
{
public double X { get; set; }
public double Y { get; set; }
public double Speed { get; set; } = 10;
public double Direction { get; set; }
public bool IsActive { get; set; } = true;
public object Clone() => new Bullet
{
Speed = Speed,
Direction = Direction,
IsActive = true
};
}
// 游戏循环中使用
var bulletPrototype = new Bullet { Speed = 10, Direction = 0 };
var bulletPool = new ObjectPool<Bullet>(bulletPrototype, maxSize: 200);
// 发射子弹
var bullet = bulletPool.Rent();
bullet.X = playerX;
bullet.Y = playerY;
bullet.Direction = aimDirection;
// 子弹消失时回收
// bullet.IsActive = false;
// bulletPool.Return(bullet);最佳实践
- 优先使用不可变对象:如果原型对象不可变,克隆就不存在浅拷贝的数据污染风险。
- 深拷贝时注意循环引用:JSON 序列化会抛出异常,需要特殊处理。
- 原型注册表使用依赖注入:将常用原型在启动时注册,通过 DI 注入到需要的服务中。
- 实现 ICloneable 时标注深浅拷贝:在文档注释中明确说明 Clone 是深拷贝还是浅拷贝。
- 考虑使用 record 类型:C# record 的
with表达式提供了简洁的非破坏性修改。
优点
缺点
总结
原型模式通过 MemberwiseClone() 实现浅拷贝,通过手动复制或 JSON 序列化实现深拷贝。原型注册表管理预定义原型,客户端通过键值克隆。C# 中可利用 System.Text.Json 序列化实现通用深拷贝。注意浅拷贝对引用类型的共享陷阱。建议在对象初始化成本高、需要创建大量相似对象的场景使用原型模式。
原型模式的本质价值在于:当你需要一个与已有对象相同或相似的新对象时,克隆比从零构造更高效。这在模板管理、配置复制、游戏对象创建等场景中尤其有用。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
- 框架与语言特性类主题要同时理解运行方式和工程组织方式。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
- 明确项目入口、配置管理、依赖管理、日志和测试策略。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
- 把 notebook 或脚本风格直接带入长期维护项目。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
- 继续补齐部署、打包、监控和性能调优能力。
适用场景
- 当你准备把《原型模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《原型模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《原型模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《原型模式》最大的收益和代价分别是什么?
