1 | 原型模式概述
原型模式是一种特殊的 创建型模式,它通过复制一个已有对象来获取更多相同或相似的对象。
原型模式可以提高系统同类型对象的创建效率,简化创建过程。
《西游记》中”孙悟空拔毛变猴“的故事几乎人人皆知,孙悟空用猴毛根据自己的形象,复制出很多和自己长的一摸一样的”分身“。类似这种场景在面向对象的软件设计领域被称为原型模式,孙悟空则被成为原型对象。
1.1 原型模式的定义
- 原型模式:使用原型实例指定待创建对象的类型,并通过复制这个原型来创建新的对象。
Prototype Pattern:Specify the kinds of objects to create using a protptypical instance, and create new objects by copying this protptype
.
1.2 原型模式的工作原理
将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现新对象的创建过程。这种创建新对象的过程也称为”克隆对象“,创建新对象的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现。
注意:通过克隆对象创建的对象是全新的对象,他们在内存中拥有新的地址。通常对克隆所产生的新对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过对克隆对象进行修改后,可以得到一系列相似但不完全的对象。
2 | 原型模式的结构与实现
2.1 原型模式的结构
原型模式包含以下 3
个角色:
- (1) Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至可以是具体实现类。
- (2) ConcretePrototvpe(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
- (3) Client(客户类):在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对负由干家白米社对抽鱼百用版
Prntotvne
编程,因此,用户可以根据需要选择具体原米,系经且有校缸的可扩民抖画加成百推几休原型类都很方便。
2.2 原型模式的浅克隆与深克隆
根据在复制原型对象的同时是否复制包含在原型对象中引用类型的成员变量,原型模式的克隆机制分为两种:浅克隆(Shallow Clone
) 和深克隆(Deep Clone
),有时也称作 浅拷贝
和 深拷贝
。
(1)浅克隆(Shallow Clone)
在浅克隆中,如果原型对象的成员亦量是值类刑(如 int、double、byte、bool 、char
等基本数据类型)将复制一份给克降对象,如果原型对象的成员变量是引用类型(如类、接口、数组等复杂数据类型),则将引用对象的地址复制一份给克降对象,也就是说,原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。
(2)深克隆(Deep Clone)
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将被复制。
2.2 原型模式的实现
实现原型模式的关键在于如何实现克隆方法,在 C#
中有两种常用的克隆实现方法:
2.2.1 通用克隆实现方法
典型示例代码如下:
abstract class Prototype
{
public abstract Prototype Clone();
}
class ConcretePrototype : Prototype
{
/// <summary>
/// 成员变量
/// </summary>
public string Attr { get; set; }
/// <summary>
/// 克隆方法,通过赋值的方式来实现对象的复制。
/// </summary>
/// <returns></returns>
public override Prototype Clone() => new ConcretePrototype
{
Attr = Attr //成员变量赋值
};
}
客户端调用示例代码:
// 1.创建 ConcretePrototype 对象作为原型。
ConcretePrototype prototype = new ConcretePrototype();
// 2. 原型实例对象 prototype 调用克隆方法 Clone() 创建克隆对象。
ConcretePrototype copy = (ConcretePrototype)prototype.Clone();
注意:此方法是原型模式的通用方法,与编程语言本身的特性无关,除 C#
外,其他面向对象编程语言也可以使用这种形式来实习原型对象的克隆。上面的克隆方法 Clone()
中,如果通过创建一个全新的成员对象来实现复制,则是一种深克隆实现方案。C#
语言中的字符串 (string/String
)对象存在特殊性,只要两个字符串的内同相同,无论是值赋值还是创建新对象,它们在内存中始终只有一份。了解更多可查看 C# 字符串驻留机制。
参考:【字符串的不可变性和驻留机制】 https://www.cnblogs.com/SignX/p/10933482.html
2.2.2 C# 中的 MemberwiseClone() 方法和 ICloneable 接口
在 C#
语言中,提供了一个 MemberwiseClone()
方法用于实现浅克隆,该方法使用很方便,之间调用一个已有对象的 MemberwiseClone()
方法即可实现对象克隆。
示例代码如下:
class Member { }
class ConcretePrototypeA
{
/// <summary>
/// 成员变量
/// </summary>
public Member MyMember { get; set; }
/// <summary>
/// 克隆方法,通过赋值的方式来实现对象的复制。
/// </summary>
/// <returns></returns>
public ConcretePrototypeA Clone() => (ConcretePrototypeA)this.MemberwiseClone(); //浅克隆
}
客户端调用,测试输出结果证实该克隆方法是浅克隆。
ICloneable
接口充当了抽象原型类的角色,具体原型类通常作为实现该接口的子类实现深克隆。
示例代码如下:
class ConcretePrototypeB: System.ICloneable
{
/// <summary>
/// 成员变量
/// </summary>
public Member MyMember { get; set; }
/// <summary>
/// 实现深克隆
/// </summary>
/// <returns></returns>
public object Clone()
{
ConcretePrototypeB copy = this.MemberwiseClone() as ConcretePrototypeB; //对象转换
Member newMember = new Member();
copy.MyMember = newMember;
return copy;
}
}
客户端调用,测试输出结果证实该克隆方法是深克隆。
3 | 原型管理器(Prototype Manager)
原型管理器(Prototype Manager
)将多个原型对象存储在一个集合中供客户端使用,他是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某一个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象类进行编程,方便扩展,该结构如下:
原型管理器 PrototypeManager
代码实现:
using System.Collections;
namespace PrototypePattern
{
/// <summary>
/// 原型管理器-PrototypeManager
/// </summary>
class PrototypeManager
{
#region SingleProfit 单例模式
//创建私有化静态obj锁
private static readonly object _ObjLock = new object();
//创建私有静态字段,接收类的实例化对象
private static volatile PrototypeManager _SingleProfit = null; //volatile 促进线程安全,保证线程有序执行
//构造函数私有化
private PrototypeManager() { }
//创建单利对象资源并返回
public static PrototypeManager CreateSingleProfitObj()
{
if (_SingleProfit == null)
{
lock (_ObjLock)
{
if (_SingleProfit == null)
{
_SingleProfit = new PrototypeManager();
}
}
}
return _SingleProfit;
}
#endregion
/// <summary>
/// Hashtable 存储原型对象
/// </summary>
private readonly static Hashtable hashTable = new Hashtable();
/// <summary>
/// Hashtable 新增原型对象
/// </summary>
/// <param name="key"></param>
/// <param name="prototype"></param>
public void Add(string key, Prototype prototype)
{
hashTable.Add(key,prototype);
}
/// <summary>
/// 获取克隆对象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public Prototype Get(string key)
{
Prototype copy = ((Prototype)hashTable[key]).Clone(); //通过(内置)克隆方法创建新对象
return copy;
}
}
}
在实际的开发中,PrototypeManager
类通常设计为单例类(单例模式),确保系统中有且仅有一个 PrototypeManager
对象,既有利于节省系统资源,还可以更好地对原型管理器对象进行控制。
添加两个类分别是 ConcretePrototypeC
和 ConcretePrototypeD
:
#region 配合 PrototypeManager 使用
class ConcretePrototypeC : Prototype
{
public override Prototype Clone()
{
return (ConcretePrototypeC) this.MemberwiseClone();
}
}
class ConcretePrototypeD : Prototype
{
public override Prototype Clone()
{
return (ConcretePrototypeD) this.MemberwiseClone();
}
}
#endregion
客户端调用方法:
完整代码示例请查看=》 https://gitee.com/dolayout/DesignPatternOfCSharp/tree/master/DesignPatternOfCSharp/PrototypePattern
4 | 原型模式的优缺点与适用环境
原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中的应用较为泛,很多软件提供的复制(Ctrl+C
)和粘贴(Ctrl+V
)操作就是原型模式的典型应用。
4.1 原型模式的主要优点
- (1)当要创建的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过制一个已有实例可以提高新实例的创建效率。
- (2)扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统没有任何影响。
- (3)原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- (4)可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其态保存起来,以便在需要的时候使用(例如恢复到某一历史状态),可辅助实现撤销操作。
4.2 原型模式的主要缺点
- (1)需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已的类进行改造时,需要修改源代码,违背了开闭原则。
- (2)在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
4.3 原型模式的适用环境
- (1)创建新对象成本较大(例如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改。
- (2)系统要保存对象的状态,而对象的状态变化很小。
- (3)需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。