引言
我们在学习每一个设计模式之前,我们就应该带着问题去学习,这样才会找到我们想要的答案,而且让我们理解的更透彻,记忆的更深刻,比如:解决了一个什么问题,使用场景等。而最好的方法就是通过例子去敲代码去感受,去理解。
案列
供应商的信息类:供应商名称,供应商经营范围,供应商编号。现在假设我们需要复制三份供应商对象
初始代码
static void Main(string[] args)
{
Provider provider=new Provider();
provider.Name = "长江红公司";
provider.Code = "NB001";
provider.JinYinFanWei = "计算机相关硬件,服务器等等";
provider.GetProviderData();
}
public class Provider
{
public string Name { get; set; }
public string Code { get; set; }
public string JinYinFanWei { get; set; }//经营范围
public void SetProviderData(string mName, string mCode, string mJinYinFanWei)
{
this.Name= mName;
this.Code= mCode;
this.JinYinFanWei= mJinYinFanWei;
}
public void GetProviderData()
{
Console.WriteLine("编号为"+Code+":"+Name+"的供应商的经营范围:"+JinYinFanWei);
}
}
现在我们需要复制三份
错误的做法:
Provider provider1 = new Provider();
Provider provider2 = new Provider();
这里虽然我们复制了三份对象,结果没问题,但是在这里provider1和provider2你只是把provider的引用传给了她们两个,在我们的内存中指向的是一块空间。而非复制了三份对象
中规中矩
static void Main(string[] args)
{
Provider provider=new Provider();
provider.Name = "长江红公司";
provider.Code = "NB001";
provider.JinYinFanWei = "计算机相关硬件,服务器等等";
provider.GetProviderData();
Provider provider1 = new Provider();
provider1.Name = "长江红公司";
provider1.Code = "NB001";
provider1.JinYinFanWei = "计算机相关硬件,服务器等等";
provider1.GetProviderData();
Provider provider2 = new Provider();
provider2.Name = "长江红公司";
provider2.Code = "NB001";
provider2.JinYinFanWei = "计算机相关硬件,服务器等等";
provider2.GetProviderData();
}
但是这种写法存在问题,如果说我的需求改变了,我让你复制30份,那这样岂不是我们需要复制30分,如果说你发现这个公司的编号录入错了,那你就需要改30份,如果要复制300份呢,而且这样的代码是冗余的。
原型模式上场
原型模式的价值就是可以创建对象的同时,而且还能保证性能
通过给出一个原型对象来指明所要创建对象的类型,然后用复制这个对象的方法来创建出更多的同类型的方法
static void Main(string[] args)
{
Provider provider = new Provider("大阳公司", "NB002", "计算机相关硬件");
//使用浅克隆
Provider provider1 = (Provider)provider.Clone();
Console.WriteLine(provider1.Name);
}
public abstract class ProviderProtype
{
public string Name { get; set; }
public string Code { get; set; }
public string JinYinFanWei { get; set; }//经营范围
public ProviderProtype(string name, string code, string jinYinFanWei)
{
Name = name;
Code = code;
JinYinFanWei = jinYinFanWei;
}
public abstract ProviderProtype Clone();
}
public class Provider:ProviderProtype
{
public Provider(string name, string code, string jinYinFanWei):base(name, code, jinYinFanWei)
{
}
//克隆的方法
public override ProviderProtype Clone()
{
//自带的浅克隆方法:
//当我们调用该方法后,会把对象里面的值类型复制一份搞一份新的
//值类型会创建一份新的,引用类型只会复制他的引用,而不是对象
//而我们的深拷贝不同点就是:对引用类型的处理,深拷贝就会复制其对象
return (ProviderProtype)this.MemberwiseClone();
}
}
其实所谓的原型模式就是实现我们的clone方法,在这个方法里面我们调用这个浅拷贝的方法,并且将其强制转换为我们返回的对象就OK了。
但是我们会发现,我们的父类在这没什么用,只是提供了一个克隆的方法,我们将其可以进行简化
static void Main(string[] args)
{
Provider provider = new Provider();
provider.Name = "Test";
provider.Code = "NB001";
provider.JinYinFanWei = "CESHI";
Provider provider1 = (Provider)provider.Clone();
Console.WriteLine(provider1.Name);
provider1.ShowProvider();
}
///简化
//我们只需要让我们的类继承ICloneable这个接口,实现接口,再调用浅拷贝
public class Provider:ICloneable
{
public string Name { get; set; }
public string Code { get; set; }
public string JinYinFanWei { get; set; }//经营范围
public void SetProviderder(string name, string code, string jinYinFanWei)
{
Name = name;
Code = code;
JinYinFanWei = jinYinFanWei;
}
public void ShowProvider()
{
Console.WriteLine(Code+Name+JinYinFanWei );
}
public object Clone()
{
return (Provider)this.MemberwiseClone();
}
}
如果我们发现我们的编号写错了,我们只需要修改Porivder.code;完成以后我们的以后要复制30份那么这三份的code就已经全部改变了。但是任然会出现问题,如果我们程序里有引用类型的话,使用浅克隆是无法复制引用类型的对象。只会复制它的引用。所以我们需要深拷贝
深拷贝研究
如果我们这里只对provider2里面的setProvider调用修改了值后,会发现只修改了我的provider2,其他的竟然没修改
现在我们添加一个供应商的分类,将它设置为一个新类
static void Main(string[] args)
{
Provider provider = new Provider();
provider.Name = "Test";
provider.Code = "NB001";
provider.JinYinFanWei = "CESHI";
Provider provider1 = (Provider)provider.Clone();
Provider provider2 = (Provider)provider.Clone();
provider2.SetProviderder("长江红","BU001","这是测试");
provider2.SetProivderType("五金工具类");
provider1.ShowProvider();
provider2.ShowProvider();
}
///简化
//我们只需要让我们的类继承ICloneable这个接口,实现接口,再调用浅拷贝
public class Provider : ICloneable
{
public string Name { get; set; }
public string Code { get; set; }
public string JinYinFanWei { get; set; }//经营范围
private ProviderType providerType;
public Provider()
{
this.providerType = new ProviderType();
}
public void SetProviderder(string name, string code, string jinYinFanWei)
{
Name = name;
Code = code;
JinYinFanWei = jinYinFanWei;
}
public void SetProivderType(string mType)
{
this.providerType.mType = mType;
}
public void ShowProvider()
{
Console.WriteLine(Code+Name+JinYinFanWei+ this.providerType.mType);
}
public object Clone()
{
return (Provider)this.MemberwiseClone();
}
}
public class ProviderType
{
public string mType { get; set; }
}
这里我们使用了浅拷贝以后,结果我们只改了provider2的seiProviderType但是我们的1也跟着变了,这是因为我们的浅拷贝在拷贝引用类型的时候,只是拷贝了2的引用,而不是真的对象。
我们给providerType也加上拷贝的接口,实现接口的方法,也进行拷贝
static void Main(string[] args)
{
Provider provider = new Provider();
provider.SetProviderder("Test", "NB001", "NB001");
provider.SetProivderType("农业产品类");
Provider provider1 = (Provider)provider.Clone();
Provider provider2 = (Provider)provider.Clone();
provider2.SetProviderder("长江红","BU001","这是测试");
provider1.SetProivderType("数码产品类");
provider2.SetProivderType("五金工具类");
provider1.ShowProvider();
provider2.ShowProvider();
}
///简化
//我们只需要让我们的类继承ICloneable这个接口,实现接口,再调用浅拷贝
public class Provider : ICloneable
{
public string Name { get; set; }
public string Code { get; set; }
public string JinYinFanWei { get; set; }//经营范围
private ProviderType providerType;
public Provider()
{
this.providerType = new ProviderType();
}
//这个构造函数传入供应商类型类,直接copy一份
public Provider(ProviderType mproviderType)
{
this.providerType = (ProviderType)mproviderType.Clone();
}
public void SetProviderder(string name, string code, string jinYinFanWei)
{
Name = name;
Code = code;
JinYinFanWei = jinYinFanWei;
}
public void SetProivderType(string mType)
{
this.providerType.mType = mType;
}
public void ShowProvider()
{
Console.WriteLine(Code+Name+JinYinFanWei+ this.providerType.mType);
}
public object Clone()
{
//在创建新对象的时候,把供应商类型也copy一份
Provider provider = new Provider(this.providerType);
provider.Name = Name;
provider.Code = Code;
provider.JinYinFanWei = JinYinFanWei;
return provider;
//如果依然调用该方法,引用类型永无出头之日
//return (Provider)this.MemberwiseClone();
}
}
public class ProviderType:ICloneable
{
public string mType { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
}
这样我们也就实现了对引用类型进行的拷贝,而不再是单单的拷贝引用类型的引用。而是对象也拷贝了。
拷贝引用类型代码梳理
我们在main方法里new provider的对像的时候,会执行他无参的构造函数,而在这个构造函数里我们把现有的this.providerType = new ProviderType();给他创建一个新对象。并且复制给我们的这个字段。
随后我们执行完构造函数,调用两个方法给我们的属性初始化。
随后执行我们最核心的一个代码:分别调用了clone的方法。
在我们的clone方法里,我们new 了个新的Provider,并且执行了私有的构造函数里,这个时候我们进入私有构造函数,我们对我们的供应商分类字段进行了拷贝。
最后我们每一个新的供应商对象,都指向了一个新的引用对象。
其实这里我们的是伪深拷贝,如果我们providerType里面的属性还是引用类型的话,那我们就是递归的再进行拷贝。如何真正的实现深拷贝呢,这就需要用到反射和我们的序列化。路漫漫其修远兮!
总结
我们的原型模式就是方便我们创建多个对象,并且提高效率
实现的方式有两种:浅拷贝和深拷贝
浅拷贝:
对于值类型是搞一个新的,对于引用类型而言只是搞一个引用
深拷贝:
不管值、引用类型都会搞一个新的
具体我们需要使用浅拷贝或者深拷贝,这就需要我们结合实际开发场景来选择。