文章目录
- 一、原型模式简介
- 二、原型模式的定义与结构
- 2.1 定义
- 2.2 结构图
- 2.3 主要角色
- 三、C#中的原型模式实现
- 3.1 使用ICloneable接口
- 3.2 自定义克隆接口
- 四、浅拷贝与深拷贝
- 4.1 浅拷贝(Shallow Copy)
- 4.2 深拷贝(Deep Copy)
- 4.3 浅拷贝与深拷贝的比较
- 五、原型模式适用场景
- 六、实际应用示例
- 6.1 文档编辑器中的复制功能
- 6.2 游戏中的角色原型
- 七、原型模式的优缺点
- 7.1 优点
- 7.2 缺点
- 八、原型模式与其他设计模式的关系
- 九、总结
- 学习资源
一、原型模式简介
原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过实例化类来创建。这种模式特别适用于当对象的创建过程复杂或成本较高时,通过克隆现有对象来提高效率。
原型模式的核心思想来源于现实生活中的"复制"行为。就像孙悟空拔下猴毛变出许多分身一样,原型模式允许我们基于一个原型对象快速创建多个相似的对象。
二、原型模式的定义与结构
2.1 定义
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2.2 结构图
2.3 主要角色
- 抽象原型(Prototype):声明一个克隆自身的接口
- 具体原型(ConcretePrototype):实现克隆的具体操作
- 客户端(Client):通过调用原型的克隆方法来获取新的对象
三、C#中的原型模式实现
在C#中,实现原型模式通常有两种方式:
- 使用C#内置的
ICloneable
接口 - 自定义克隆接口和方法
3.1 使用ICloneable接口
C#框架提供了ICloneable
接口,该接口包含一个Clone()
方法,用于创建对象的副本。
using System;// 实现ICloneable接口的具体原型类
public class Person : ICloneable
{public string Name { get; set; }public int Age { get; set; }public Address HomeAddress { get; set; }public Person(string name, int age, Address address){Name = name;Age = age;HomeAddress = address;}// 实现浅拷贝public object Clone(){// MemberwiseClone()是Object类的一个保护方法,它创建当前对象的浅表副本return this.MemberwiseClone();}// 实现深拷贝public Person DeepClone(){Person clone = (Person)this.MemberwiseClone();// 手动创建引用类型的新实例clone.HomeAddress = new Address(HomeAddress.Street, HomeAddress.City, HomeAddress.ZipCode);return clone;}
}public class Address
{public string Street { get; set; }public string City { get; set; }public string ZipCode { get; set; }public Address(string street, string city, string zipCode){Street = street;City = city;ZipCode = zipCode;}
}// 客户端代码
class Program
{static void Main(string[] args){// 创建原型对象Address address = new Address("123 Main St", "Beijing", "100000");Person person1 = new Person("张三", 30, address);// 浅拷贝Person person2 = (Person)person1.Clone();person2.Name = "李四";person2.HomeAddress.Street = "456 Elm St"; // 注意:这会改变person1的地址!// 深拷贝Person person3 = person1.DeepClone();person3.Name = "王五";person3.HomeAddress.Street = "789 Oak St"; // 不会影响person1的地址// 输出结果Console.WriteLine($"Person1: {person1.Name}, Address: {person1.HomeAddress.Street}");Console.WriteLine($"Person2: {person2.Name}, Address: {person2.HomeAddress.Street}");Console.WriteLine($"Person3: {person3.Name}, Address: {person3.HomeAddress.Street}");}
}
3.2 自定义克隆接口
using System;// 自定义的原型接口
public interface IPrototype<T>
{T Clone();
}// 具体原型类
public class Document : IPrototype<Document>
{public string Title { get; set; }public string Content { get; set; }public DocumentInfo Info { get; set; }public Document(string title, string content, DocumentInfo info){Title = title;Content = content;Info = info;}// 浅拷贝public Document Clone(){return (Document)this.MemberwiseClone();}// 深拷贝public Document DeepClone(){Document clone = (Document)this.MemberwiseClone();// 手动创建DocumentInfo的新实例clone.Info = new DocumentInfo(Info.Author, Info.CreationDate);return clone;}
}public class DocumentInfo
{public string Author { get; set; }public DateTime CreationDate { get; set; }public DocumentInfo(string author, DateTime creationDate){Author = author;CreationDate = creationDate;}
}// 客户端代码
class Program
{static void Main(string[] args){// 创建原型对象DocumentInfo info = new DocumentInfo("张三", DateTime.Now);Document doc1 = new Document("设计模式", "原型模式的内容...", info);// 浅拷贝Document doc2 = doc1.Clone();doc2.Title = "设计模式副本";doc2.Info.Author = "李四"; // 修改会影响doc1// 深拷贝Document doc3 = doc1.DeepClone();doc3.Title = "设计模式另一副本";doc3.Info.Author = "王五"; // 不会影响doc1// 输出结果Console.WriteLine($"Doc1: {doc1.Title}, Author: {doc1.Info.Author}");Console.WriteLine($"Doc2: {doc2.Title}, Author: {doc2.Info.Author}");Console.WriteLine($"Doc3: {doc3.Title}, Author: {doc3.Info.Author}");}
}
四、浅拷贝与深拷贝
在原型模式中,理解浅拷贝和深拷贝的区别至关重要:
4.1 浅拷贝(Shallow Copy)
浅拷贝只复制对象的值类型字段和引用类型字段的引用,而不复制引用类型字段所指向的对象。
- C#中可以通过
Object.MemberwiseClone()
方法实现 - 优点:实现简单,性能高
- 缺点:当原型对象中包含引用类型字段时,克隆对象与原型对象会共享这些引用类型的实例
4.2 深拷贝(Deep Copy)
深拷贝不仅复制对象的值类型字段和引用类型字段的引用,还递归地复制所有引用类型字段所指向的对象。
- 实现方式:
- 手动创建所有引用类型成员的新实例
- 通过序列化和反序列化(适合复杂对象)
// 使用序列化实现深拷贝
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;[Serializable]
public class Employee : ICloneable
{public string Name { get; set; }public Department Department { get; set; }public object Clone(){return this.MemberwiseClone(); // 浅拷贝}public Employee DeepClone(){// 使用序列化和反序列化实现深拷贝using (MemoryStream stream = new MemoryStream()){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, this);stream.Position = 0;return (Employee)formatter.Deserialize(stream);}}
}[Serializable]
public class Department
{public string Name { get; set; }public string Location { get; set; }
}
注意:使用
BinaryFormatter
进行序列化在.NET 5+版本中被标记为过时,建议在实际开发中使用其他序列化方式,如JSON序列化。
4.3 浅拷贝与深拷贝的比较
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
性能 | 高 | 较低 |
实现复杂度 | 简单 | 复杂 |
内存占用 | 低 | 高 |
适用场景 | 对象中只有值类型或不关心引用类型的共享 | 需要完全独立的对象副本 |
五、原型模式适用场景
-
对象创建成本高:当对象的创建过程很耗资源(如需要访问数据库、读取大文件等)时,克隆已有对象更高效。
-
系统需要独立于产品创建过程:客户端不需要知道对象创建的细节,只需要一个原型对象的引用。
-
对象类型在运行时确定:当系统需要在运行时确定创建哪种类型的对象时,原型模式特别有用。
-
需要大量相似对象:如游戏中的相似角色、配置系统中的相似配置等。
-
需要保存对象的特定状态:有时候我们需要保存对象的某个状态,以便将来能够恢复到这个状态。
六、实际应用示例
6.1 文档编辑器中的复制功能
using System;
using System.Collections.Generic;// 文档元素接口
public interface IDocumentElement : ICloneable
{void Render();
}// 具体元素:文本
public class TextElement : IDocumentElement
{public string Content { get; set; }public string FontFamily { get; set; }public int FontSize { get; set; }public TextElement(string content, string fontFamily, int fontSize){Content = content;FontFamily = fontFamily;FontSize = fontSize;}public object Clone(){return new TextElement(Content, FontFamily, FontSize);}public void Render(){Console.WriteLine($"渲染文本: '{Content}', 字体: {FontFamily}, 大小: {FontSize}pt");}
}// 具体元素:图像
public class ImageElement : IDocumentElement
{public string Source { get; set; }public int Width { get; set; }public int Height { get; set; }public ImageElement(string source, int width, int height){Source = source;Width = width;Height = height;}public object Clone(){return new ImageElement(Source, Width, Height);}public void Render(){Console.WriteLine($"渲染图像: {Source}, 尺寸: {Width}x{Height}px");}
}// 文档类
public class Document : ICloneable
{public string Title { get; set; }public List<IDocumentElement> Elements { get; set; } = new List<IDocumentElement>();public void AddElement(IDocumentElement element){Elements.Add(element);}public void Render(){Console.WriteLine($"文档: {Title}");Console.WriteLine("内容:");foreach (var element in Elements){element.Render();}}public object Clone(){Document newDoc = new Document { Title = $"{Title} - 副本" };// 深拷贝所有元素foreach (var element in Elements){newDoc.AddElement((IDocumentElement)element.Clone());}return newDoc;}
}// 客户端代码
class Program
{static void Main(string[] args){// 创建原始文档Document originalDoc = new Document { Title = "设计模式研究" };originalDoc.AddElement(new TextElement("原型模式介绍", "Arial", 12));originalDoc.AddElement(new ImageElement("diagram.png", 800, 600));originalDoc.AddElement(new TextElement("原型模式适用于...", "Times New Roman", 10));// 渲染原始文档originalDoc.Render();Console.WriteLine();// 克隆文档Document clonedDoc = (Document)originalDoc.Clone();// 修改克隆文档((TextElement)clonedDoc.Elements[0]).Content = "原型模式深入研究";// 渲染克隆文档clonedDoc.Render();}
}
6.2 游戏中的角色原型
using System;// 角色接口
public interface ICharacter
{ICharacter Clone();void Display();
}// 具体角色:战士
public class Warrior : ICharacter
{public string Name { get; set; }public int Health { get; set; }public int Attack { get; set; }public string Weapon { get; set; }public Warrior(string name, int health, int attack, string weapon){Name = name;Health = health;Attack = attack;Weapon = weapon;}public ICharacter Clone(){return new Warrior(Name, Health, Attack, Weapon);}public void Display(){Console.WriteLine($"战士: {Name}, 生命值: {Health}, 攻击力: {Attack}, 武器: {Weapon}");}
}// 具体角色:法师
public class Mage : ICharacter
{public string Name { get; set; }public int Health { get; set; }public int Mana { get; set; }public string Spell { get; set; }public Mage(string name, int health, int mana, string spell){Name = name;Health = health;Mana = mana;Spell = spell;}public ICharacter Clone(){return new Mage(Name, Health, Mana, Spell);}public void Display(){Console.WriteLine($"法师: {Name}, 生命值: {Health}, 魔法值: {Mana}, 法术: {Spell}");}
}// 角色原型管理器
public class CharacterManager
{private readonly Dictionary<string, ICharacter> _characterPrototypes = new Dictionary<string, ICharacter>();public void AddPrototype(string key, ICharacter character){_characterPrototypes[key] = character;}public ICharacter GetPrototype(string key){if (!_characterPrototypes.ContainsKey(key)){throw new ArgumentException($"没有找到键为'{key}'的原型");}return _characterPrototypes[key].Clone();}
}// 客户端代码
class Program
{static void Main(string[] args){// 创建原型管理器CharacterManager manager = new CharacterManager();// 注册原型manager.AddPrototype("elite_warrior", new Warrior("精英战士", 200, 30, "大刀"));manager.AddPrototype("elite_mage", new Mage("精英法师", 100, 150, "火球术"));// 从原型创建实例并自定义ICharacter warrior1 = manager.GetPrototype("elite_warrior");((Warrior)warrior1).Name = "战士1";ICharacter warrior2 = manager.GetPrototype("elite_warrior");((Warrior)warrior2).Name = "战士2";((Warrior)warrior2).Weapon = "巨斧";ICharacter mage1 = manager.GetPrototype("elite_mage");((Mage)mage1).Name = "法师1";// 显示所有角色warrior1.Display();warrior2.Display();mage1.Display();}
}
七、原型模式的优缺点
7.1 优点
- 减少子类创建:原型模式不需要创建与产品层次相同的工厂层次。
- 提高性能:克隆对象通常比创建新对象更高效。
- 动态添加和删除对象:可以在运行时动态添加或删除原型。
- 隐藏具体产品类:客户端无需知道具体产品类的信息。
- 复制复杂对象:可以复制具有复杂内部结构的对象。
7.2 缺点
- 深拷贝复杂度:实现深拷贝可能会很复杂,特别是对象包含循环引用时。
- 克隆方法实现困难:对每个类都需要实现克隆方法,对于已有的类改造可能比较困难。
- 不支持final字段克隆:在某些语言中,不支持克隆包含final字段的对象。
八、原型模式与其他设计模式的关系
-
与工厂模式的关系:原型模式通常与工厂方法模式结合使用,工厂方法创建原型实例,然后通过原型的克隆方法创建新对象。
-
与建造者模式的关系:建造者模式关注的是"如何一步一步构建一个复杂对象",而原型模式关注的是"如何复制一个已有对象"。
-
与命令模式的关系:在实现撤销/重做功能时,常常需要保存对象状态的快照,这时可以使用原型模式。
九、总结
原型模式通过克隆现有对象来创建新对象,避免了重新初始化的过程,特别适合对象创建成本高或需要保存对象状态的场景。在C#中,我们可以通过实现ICloneable
接口或自定义接口来实现原型模式,同时需要注意浅拷贝和深拷贝的使用场景与实现方式。
原型模式的关键在于理解何时使用浅拷贝,何时使用深拷贝,以及如何正确地实现它们。合理应用原型模式,可以使我们的代码更加灵活、高效。
学习资源
- Microsoft Docs: ICloneable Interface
- Refactoring Guru: Prototype Pattern
- Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides