设计模式之创建型(6)-原型模式(Prototype)
(一)定义:Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
1.1 UML类图
1.2 类与对象之间的关系
Prototype:抽象原型基类,定义具有克隆自己的方法的接口。
ConcretePrototype:具体原型类,实现具体克隆方法。
Client:客户程序,通过克隆生成一个新的对象。
1.3 典型调用顺序图
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
代码public abstract class Prototype { private string id; public Prototype(string id) { this.id = id; } public string ID { get { return this.id; } } public abstract Prototype Clone(); } public class ConcretePrototype1 : Prototype { private System.Data.DataTable dt = new System.Data.DataTable(); public ConcretePrototype1(string id) : base(id) { } public override Prototype Clone() { return (Prototype)this.MemberwiseClone(); } } public class Client { ConcretePrototype1 p1 = null; ConcretePrototype1 c1 = null; public Client() { p1 = new ConcretePrototype1("p1"); c1 = (ConcretePrototype1)p1.Clone(); } public override string ToString() { bool isbool = Object.ReferenceEquals(p1, c1); return string.Format("p1.id={0} HashCode={1}\r\nc1.id={2} HashCode={3}", p1.ID, p1.GetHashCode(), c1.ID, c1.GetHashCode()); } }
(二) 原型模式中的浅拷贝和深拷贝(C#语言实现)
2.1 new操作做了什么
new操作符可以创建值类型变量、引用类型对象,同时自动调用构造函数。
例子:假设有一个Point类,有x,y两个变量。以下动画演示了在new操作过程中的内存分配的过程。其中的内存布局图如下:
从以上图内存布局图示可以看到p中存储的是一个指向Point对象的地址值。当调用new操作符时会在内存中分配连续地址来建立Point类的内存空间。然后将首地址空间赋值给指向的变量。
假定现有一个Rect类,由两个点组成。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
代码public class Point { public int x; public int y; public Point() { } public Point(int x, int y) { this.x = x; this.y = y; } } public class Rect { public Point startPoint = null; public Point endPoint = null; public Rect() { startPoint = new Point(0, 0); endPoint = new Point(10, 10); } }
其内存变化如下图:
2.2 程序运行的内存区域
一个程序将操作系统分配给其运行的内存块分为4个区域:
1)代码区:存放程序的代码,即程序中的各个函数代码块。
2)全局数据区:存放程序的全局数据和静态数据。
3)堆区:存放程序的动态数据。在C/C++中,用alloc系统函数和new申请的内存都存在于堆中。在C#语言中有堆和托管堆的区别。
4)栈区:存放程序的局部数据,即各个函数中的数据。
2.3 Object.MemberwiseClone()方法的使用
MemberwiseClone()方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。
例如,考虑一个名为 X 的对象,该对象引用对象 A 和 B。对象 B 又引用对象 C。X 的浅表副本创建一个新对象 X2,该对象也引用对象 A 和 B。与此相对照,X 的深层副本创建一个新对象 X2,该对象引用新对象 A2 和 B2,它们分别是 A 和 B 的副本。B2 又引用新对象 C2,C2 是 C 的副本。使用实现 ICloneable 接口的类执行对象的浅表或深层复制。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
浅表拷贝代码public class X { public A a = null; public B b = null; public X() { a = new A(); b = new B(); } public X Clone() { return (X)this.MemberwiseClone(); } } public class A { public A() { } } public class B { public C c = null; public B() { c = new C(); } } public class C { public C() { } }
2.4 深拷贝的几种实现方式
2.4.1:利用MemberwiseClone()方法特性(.NET中值类型默认是深拷贝的,而对于引用类型,默认实现的是浅拷贝。所以对于类中引用类型的属性改变时,其另一个对象也会发生改变。)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
代码演示public class X : ICloneable { public A a = null; public B b = null; public X() { a = new A(); b = new B(); } private X(A a, B b) { this.a = (A)a.Clone(); this.b = (B)b.Clone(); } public object Clone() { //浅拷贝 //return this.MemberwiseClone(); //深拷贝 X x = new X(this.a, this.b); return x; } } public class A : ICloneable { private string a = ""; public A() { } public string PA { get { return this.a; } set { this.a = value; } } public object Clone() { return this.MemberwiseClone(); } } public class B : ICloneable { private string b = ""; public C c = null; public B() { c = new C(); } public string PB { get { return this.b; } set { this.b = value; } } public object Clone() { return this.MemberwiseClone(); } } public class C : ICloneable { private string c = ""; public C() { } public string PC { get { return this.c; } set { this.c = value; } } public object Clone() { return this.MemberwiseClone(); } }
代码调试动画分析:从代码调试看出:实现Clone方法后创建得到的对象a中存储的字段值还是“123”当再次赋值为“456”时x2对象的字段值改变,没有影响到x1对象的“123”值。以上代码存在的问题是如果此时对C类的字段c进行赋值改变会同时影响到x1,x2对象的值(如右图)
只需在B中添加和修改以下代码即可:(.NET中值类型默认是深拷贝的,而对于引用类型,默认实现的是浅拷贝。所以对于类中引用类型的属性改变时,其另一个对象也会发生改变。)
以上方式存在的问题:如果类间的依赖关系太复杂,则每个与之相关的类成员变量都需要实现Clone方法进行非值类型的深拷贝。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
代码private B(C c) { this.c = (C)c.Clone(); } public object Clone() { //B b = new B(c); return new B(c); //return this.MemberwiseClone(); }
2.4.2:利用反射原理实现深拷贝
2.4.3