深入详解原型模式之深复制和浅复制
了解GOF写的设计模式中的原型模式都知道其实它讲的就是对象的克隆(Clone).
《设计模式》里写道:原型模式的意图是:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
其实它讲的就是深度复制,即复制一个现有的对象,改变复制后的属性或字段不影响模型对象.废话不多讲我们直接用一个简单的例子来看看这倒底是个神马意思.我们用一个例子来讲解.
假如现在要你打印出班级学生的所有详细信息.
假设学生信息只有学号、姓名、班级.
我们先打印一个学生的成绩.代码如下:
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Student s1 = new Student("001", "张三", "高三(3)班");
s1.Print();
}
}
class Student
{
private string _sno;
private string _name;
private string _classname;
public string Sno
{
get { return _sno; }
set { _sno = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Classname
{
get { return _classname; }
set { _classname = value; }
}
public Student()
{
}
public Student(string sno, string name, string classname)
{
Sno = sno;
Name = name;
Classname = classname;
}
public void Print()
{
Console.WriteLine("{0} {1} {2}", Sno, Name, Classname);
}
}
}
结果:
那么打印多个学生的话那么可以直接在Main函数里实例化学生即:
{
Student s1 = new Student("001", "张三", "高三(3)班");
s1.Print();
Student s2 = new Student("002", "李四", "高三(3)班");
s2.Print();
.
.
.
}
但是现在要你动态的复制(Clone)生成对象的话那又该怎么写呢?代码如下
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Student s1 = new Student("001", "张三", "高三(3)班");
Student s2 = (Student)s1.Clone();
s2.Sno = "002";
s2.Name = "李四";
s2.Print();
}
}
class Student : ICloneable
{
private string _sno;
private string _name;
private string _classname;
public string Sno
{
get { return _sno; }
set { _sno = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Classname
{
get { return _classname; }
set { _classname = value; }
}
{
}
public Student(string sno, string name, string classname)
{
Sno = sno;
Name = name;
Classname=classname;
}
public Object Clone()
{
return this.MemberwiseClone();
}
public void Print()
{
Console.WriteLine("{0} {1} {2}", Sno,Name,Classname);
}
}
}
结果:
可能有同学要问这个this.MemberwiseClone()是个什么方法:其实他就相当于
{
//return this.MemberwiseClone();
Student obj = new Student();
obj.Sno = this.Sno;
obj.Name = this.Name;
obj.Classname = this.Classname;
return obj;
}
到这里好像是复制一个完全独立的新对象,跟我们要谈的深复制和浅负责没多大关系.哈哈,那现在转折点要来了.浅复制在不经意间出现了
如果学生类里面加上性别年龄升高并且这三个属性用Feature类来封装.看看结果又会变成什么样 代码如下:
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Student s1 = new Student("001", "张三", "高三(3)班");
s1.SetFeature("男", "176厘米", "18");
Student s2 = (Student)s1.Clone();
s2.Sno = "002";
s2.Name = "李四";
s2.SetFeature("男", "180厘米", "19");
s1.Print();
s2.Print();
}
}
class Student : ICloneable
{
private string _sno;
private string _name;
private string _classname;
private Feature _fe;
public string Sno
{
get { return _sno; }
set { _sno = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Classname
{
get { return _classname; }
set { _classname = value; }
}
public Student()
{
_fe = new Feature();
}
public Student(string sno, string name, string classname)
{
Sno = sno;
Name = name;
Classname = classname;
_fe = new Feature();
}
public void SetFeature(string sex,string high,string age)
{
_fe.Sex = sex;
_fe.High = high;
_fe.Age = age;
}
public Object Clone()
{
return this.MemberwiseClone();
}
public void Print()
{
Console.WriteLine("{0} {1} {2}", Sno, Name, Classname);
Console.WriteLine("{0} {1} {2}", _fe.Sex, _fe.High, _fe.Age);
}
}
class Feature
{
private string _sex;
private string _age;
private string _high;
public string Sex
{
get { return _sex; }
set { _sex = value; }
}
public string High
{
get { return _high; }
set { _high = value; }
}
public string Age
{
get { return _age; }
set { _age = value; }
}
public Feature()
{
}
public Feature(string sex, string high, string age)
{
Sex = sex;
High = high;
Age = age;
}
}
}
出现了我们意想不到的结果: (这就是所谓的浅复制,改变复制后的实例对象属性会影响到原型的属性值变化)
我们期望的是出现这种结果: (这就是我们期望的深复制,改变复制后的实例对象不会影响到原型的属性值)
为什么会出现这种情况呢,其实我把 public Object Clone()函数换一种写法大家就会明白问题出现在哪里了 public Object Clone()等实现方法如下:
{
//return this.MemberwiseClone();
Student obj = new Student();
obj.Sno = this.Sno;
obj.Name = this.Name;
obj.Classname = this.Classname;
obj._fe = this._fe;
return obj;
}
眼尖的同学一眼就发现了问题所在!没错,问题就出现在 obj._fe=this._fe;这句代码身上;
对象是引用类型,这句代码的意思是复制后的s2对象和原型对象s1共用一个_fe,导致s2修改了_fe里面的属性值.原型对象s1的_fe也跟着变.
系统调用 return this.MenberwiseClone()方法就是上面的那些具体代码,这样产生了浅复制.其实想复制的话代码应该是:
{
//return this.MemberwiseClone();
Student obj = new Student();
obj.Sno = this.Sno;
obj.Name = this.Name;
obj.Classname = this.Classname;
//obj._fe = this._fe;
obj._fe.Age = this._fe.Age;
obj._fe.High = this._fe.High;
obj._fe.Sex = this._fe.Sex;
return obj;
}
这样的话就是深复制就是我们期望的结果.
其实
obj._fe.Age = this._fe.Age;
obj._fe.High = this._fe.High;
obj._fe.Sex = this._fe.Sex;
这段代码是实例化一个Feature 对象在内在分配一个对象空间.好让复制后的S1和S2对象不共享同一个Feature对象而已.如果我们还希望能用到this.MenberwiseClone()方法那么该怎么改写代码呢?
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Student s1 = new Student("001", "张三", "高三(3)班");
s1.SetFeature("男", "176厘米", "18");
Student s2 = (Student)s1.Clone();
s2.Sno = "002";
s2.Name = "李四";
s2.SetFeature("男", "180厘米", "19");
s1.Print();
s2.Print();
}
}
class Student : ICloneable
{
private string _sno;
private string _name;
private string _classname;
private Feature _fe;
public string Sno
{
get { return _sno; }
set { _sno = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Classname
{
get { return _classname; }
set { _classname = value; }
}
public Student(Feature fe)
{
_fe = (Feature)fe.Clone();
}
public Student()
{
_fe = new Feature();
}
public Student(string sno, string name, string classname)
{
Sno = sno;
Name = name;
Classname = classname;
_fe = new Feature();
}
public void SetFeature(string sex,string high,string age)
{
_fe.Sex = sex;
_fe.High = high;
_fe.Age = age;
}
public Object Clone()
{
Student obj = new Student(this._fe);
obj.Sno = this.Sno;
obj.Name = this.Name;
obj.Classname = this.Classname;
//obj._fe = this._fe;
//obj._fe.Age = this._fe.Age;
//obj._fe.High = this._fe.High;
//obj._fe.Sex = this._fe.Sex;
return obj;
}
public void Print()
{
Console.WriteLine("{0} {1} {2}", Sno, Name, Classname);
Console.WriteLine("{0} {1} {2}", _fe.Sex, _fe.High, _fe.Age);
}
}
class Feature :ICloneable
{
private string _sex;
private string _age;
private string _high;
public string Sex
{
get { return _sex; }
set { _sex = value; }
}
public string High
{
get { return _high; }
set { _high = value; }
}
public string Age
{
get { return _age; }
set { _age = value; }
}
public Feature()
{
}
public Feature(string sex, string high, string age)
{
Sex = sex;
High = high;
Age = age;
}
public Object Clone()
{
return this.MemberwiseClone();
}
}
}
结果就是我们期望的:
所以要深复制的话且用到MenberwiseClone()方法的话,要保证使用MenberwiseClone()方法的对象类是没用引用类型属性(即没有类对象属性)
上面完整的代码就是著名的设计模式里的原型模式.如果你搞不懂深复制和浅复制也就淡不上搞懂原型模式了
给大家留一个思考 如果Feature 里面再包含一个兴趣类即Interest类里面包含三个兴趣属性 那么该如何深拷贝Student对象呢(也就是怎么实现原型模式)且用到MenberwiseClone()方法当然了也可以不用到.
{
private string _sex;
private string _age;
private string _high;
private Interest _interest;
}
class Interest
{
private string _it1;
private string _it2;
private string _it3;
public string It1
{
get { return _it1; }
set { _it1 = value; }
}
public string It2
{
get { return _it2; }
set { _it2 = value; }
}
public string It3
{
get { return _it3; }
set { _it3 = value; }
}
public Interest()
{
}
public Interest(string it1,string it2,string it3)
{
It1 = it1;
It2 = it2;
It3 = it3;
}
}