定义:通过复制现有实例来创建新的实例,无需知道相应类的信息。
简单地理解,其实就是当需要创建一个指定的对象时,我们刚好有一个这样的对象,但是又不能直接使用,我会clone一个一毛一样的新对象来使用;基本上这就是原型模式。关键字:Clone。
1 深拷贝和浅拷贝
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。clone明显是深复制,clone出来的对象是是不能去影响原型对象的
2 原型模式的结构和代码示例(Java)
![](https://i-blog.csdnimg.cn/blog_migrate/7d74f7e42c0559aa288c687a41675fca.png)
Client:使用者
Prototype:接口(抽象类),声明具备clone能力,例如java中得Cloneable接口
ConcretePrototype:具体的原型类
可以看出设计模式还是比较简单的,重点在于Prototype接口和Prototype接口的实现类ConcretePrototype。原型模式的具体实现:一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法。
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
//此句中的super.clone()是重点
Prototype proto = (Prototype) super.clone();
return proto;
}
}
举例(银行发送大量邮件,使用clone和不使用clone的时间对比):我们模拟创建一个对象需要耗费比较长的时间,因此,在构造函数中我们让当前线程sleep一会
public static void main(String[] args) {
int i = 0;
int MAX_COUNT = 10;
EventTemplate et = new EventTemplate("9月份信用卡账单", "国庆抽奖活动...");
long start = System.currentTimeMillis();
while (i < MAX_COUNT) {
// 以下是每封邮件不同的地方
Mail mail = new Mail(et);
mail.setContent(getRandString(5) + ",先生(女士):你的信用卡账单..." + mail.getTail());
mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
// 然后发送邮件
sendMail(mail);
i++;
}
long end = System.currentTimeMillis();
System.out.println("用时:" + (end - start));
}
用时:10001
使用clone,发送十个邮件
public static void main(String[] args) {
int i = 0;
int MAX_COUNT = 10;
EventTemplate et = new EventTemplate("9月份信用卡账单", "国庆抽奖活动...");
long start=System.currentTimeMillis();
Mail mail = new Mail(et);
while (i < MAX_COUNT) {
Mail cloneMail = mail.clone();
mail.setContent(getRandString(5) + ",先生(女士):你的信用卡账单..."
+ mail.getTail());
mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
sendMail(cloneMail);
i++;
}
long end=System.currentTimeMillis();
System.out.println("用时:"+(end-start));
}
3 .Net中的深拷贝与浅拷贝
.net提供了一个ICloneable接口,该接口下有一个Clone()方法,你可以实现它用来实现你自己的克隆方式,比如深克隆或是浅克隆,MemberwiseClone()是object类中的一个方法,用来实现类的浅克隆。
MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。深拷贝,即实现ICloneable接口.ICloneable可用于深拷贝和浅拷贝。
下面是一个深拷贝与浅拷贝的Model,仅供参考
[Serializable]
public class InvoiceDetailResponse : IDeepCopy, IShallowCopy
{
public Guid merchant_id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string uname { get; set; }
/// <summary>
/// 浅拷贝
/// </summary>
/// <returns></returns>
public object ShallowCopy()
{
return this.MemberwiseClone();
}
/// <summary>
/// 深拷贝 【不建议使用二进制流方法,此方法即使在类前面加了可序列化标志,调用该方法时也会报未序列化错误】,推荐使用反射方式
/// </summary>
/// <returns></returns>
public object DeepCopy()
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return (InvoiceDetailResponse)bFormatter.Deserialize(stream);
}
}
}
/// <summary>
/// 深拷贝接口
/// </summary>
interface IDeepCopy
{
object DeepCopy();
}
/// <summary>
/// 浅拷贝接口
/// </summary>
interface IShallowCopy
{
object ShallowCopy();
}
利用反射实现的深拷贝:
利用反射实现
public static T DeepCopyByReflection<T>(T obj)
{
if (obj is string || obj.GetType().IsValueType)
return obj;
object retval = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance);
foreach(var field in fields)
{
try
{
field.SetValue(retval, DeepCopyByReflection(field.GetValue(obj)));
}
catch { }
}
return (T)retval;
}
ICloneable接口实现示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 原型模式
{
class Helper:ICloneable
{
public int age { get; set; }
public string name { get; set; }
public Helper(int Age,string Name)
{
this.age = Age;
this.name = Name;
}
//实现Clone
public object Clone()
{
//浅复制,值类型与string(因不可变性)是直接产生一个新的副本,引用类型仅仅只复制当前实例对象的引用,结果对象与当前对象指向同一目标
return MemberwiseClone();
}
}
class FaShi : ICloneable
{
public Helper helper { get; set; }
public FaShi(Helper hlp)
{
this.helper = hlp;
}
public object Clone()
{
//深复制,值类型与string(因不可变性)是直接产生一个新的副本,引用类型产生新的对象,与原对象实例无关
return new FaShi(this.helper.Clone() as Helper);
//浅复制,值类型与string(因不可变性)是直接产生一个新的副本,引用类型仅仅只复制当前实例对象的引用,结果对象与当前对象指向同一目标
//return this.MemberwiseClone();
}
}
class Program
{
static void Main(string[] args)
{
Helper h1 = new Helper(12, "张三");
//Helper里的值类型与string是直接产生一个新的副本,所以是深复制
Helper h2 = h1.Clone() as Helper;
h2.age = 18;
Console.WriteLine("姓名:{0},年纪:{1}",h1.age,h1.name);
Console.WriteLine("姓名:{0},年纪:{1}", h2.age,h2.name);
Console.WriteLine("=====================================");
Helper h3 = new Helper(50, "李四");
FaShi fs = new FaShi(h3);
//开始复制,使用MemberwiseClone浅复制,深复制自定义实现
FaShi fs1 = fs.Clone() as FaShi;
fs1.helper.age = 29;
Console.WriteLine("姓名:{0},年纪:{1}", fs.helper.age, fs.helper.name);
Console.WriteLine("姓名:{0},年纪:{1}", fs1.helper.age, fs1.helper.name);
Console.Read();
}
}
}
3 总结
原型模式的本质就是clone,可以解决构建复杂对象的资源消耗问题,能再某些场景中提升构建对象的效率;还有一个重要的用途就是保护性拷贝,可以通过返回一个拷贝对象的形式,实现只读的限制,Java与.Net中适用(拷贝比新建实例快得多)。
说明:部分内容节先自以下站点