原型模式其实很简单,又可以成为克隆模式。想象你要造一架飞机,如果你要重头到位掌握造飞机的核心技术,那么你要从力学,材料学开始学起。但是你想快点看到成果,那你就把对应的零件买回来自己组装一个就好了。
目录
1.UML图
可以看出,在原型模式的结构图有以下角色:
(1)、原型类(Prototype):原型类,声明一个Clone自身的接口;
(2)、具体原型类(ConcretePrototype):实现一个Clone自身的操作。
在原型模式中,Prototype通常提供一个包含Clone方法的接口,具体的原型ConcretePrototype使用Clone方法完成对象的创建。
所以关键在如何实现Clone()函数。
2. 深拷贝与浅拷贝
相比大家都知道深拷贝与浅拷贝的区别,这里不过多讲述了。只是在C#里面系统提供了一个函数:
protected object MemberwiseClone ();
我们先来讲解一下这个函数,再给出案例。这个函数是一个浅拷贝函数,该方法 MemberwiseClone 通过创建新对象,然后将当前对象的非静态字段复制到新对象来创建浅表副本。 如果字段是值类型,则执行字段的逐位副本。 如果字段是引用类型,则会复制引用,但引用对象不是;因此,原始对象及其克隆引用同一对象。
官方的例子其实很典型:
using System;
public class IdInfo
{
public int IdNumber;
public IdInfo(int IdNumber)
{
this.IdNumber = IdNumber;
}
}
public class Person
{
public int Age;
public string Name;
public IdInfo IdInfo;
public Person ShallowCopy()
{
return (Person) this.MemberwiseClone();
}
public Person DeepCopy()
{
Person other = (Person) this.MemberwiseClone();
other.IdInfo = new IdInfo(IdInfo.IdNumber);
other.Name = String.Copy(Name);
return other;
}
}
public class Example
{
public static void Main()
{
// Create an instance of Person and assign values to its fields.
Person p1 = new Person();
p1.Age = 42;
p1.Name = "Sam";
p1.IdInfo = new IdInfo(6565);
// Perform a shallow copy of p1 and assign it to p2.
Person p2 = p1.ShallowCopy();
// Display values of p1, p2
Console.WriteLine("Original values of p1 and p2:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p2 instance values:");
DisplayValues(p2);
// Change the value of p1 properties and display the values of p1 and p2.
p1.Age = 32;
p1.Name = "Frank";
p1.IdInfo.IdNumber = 7878;
Console.WriteLine("\nValues of p1 and p2 after changes to p1:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p2 instance values:");
DisplayValues(p2);
// Make a deep copy of p1 and assign it to p3.
Person p3 = p1.DeepCopy();
// Change the members of the p1 class to new values to show the deep copy.
p1.Name = "George";
p1.Age = 39;
p1.IdInfo.IdNumber = 8641;
Console.WriteLine("\nValues of p1 and p3 after changes to p1:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p3 instance values:");
DisplayValues(p3);
}
public static void DisplayValues(Person p)
{
Console.WriteLine(" Name: {0:s}, Age: {1:d}", p.Name, p.Age);
Console.WriteLine(" Value: {0:d}", p.IdInfo.IdNumber);
}
}
// The example displays the following output:
// Original values of p1 and p2:
// p1 instance values:
// Name: Sam, Age: 42
// Value: 6565
// p2 instance values:
// Name: Sam, Age: 42
// Value: 6565
//
// Values of p1 and p2 after changes to p1:
// p1 instance values:
// Name: Frank, Age: 32
// Value: 7878
// p2 instance values:
// Name: Sam, Age: 42
// Value: 7878
//
// Values of p1 and p3 after changes to p1:
// p1 instance values:
// Name: George, Age: 39
// Value: 8641
// p3 instance values:
// Name: Frank, Age: 32
// Value: 7878
IdInfo是一个引用类型,所以浅拷贝是拷贝的是它的引用,当原对象中的IdInfo改变时,浅拷贝对象也会改变。
所以当你实现Clone函数时,要注意那些是值对象,那些是引用对象!
3.案例
这里我构造了一个Person抽象类:重点关注ShallowClone(),和DeepClone().
internal abstract class Person
{
public string? Name { get; set; }
public int? Age { get; set; }
public Address? Address { get; set; }
public abstract Person? ShallowClone();
public abstract Person? DeepClone();
}
然后有一个具体的Custome类派生自person,并额外多了字段:
internal class Custome:Person
{
public Order? Order { get; set; }
public Custome(string name,Address address)
{
Name = name;
Address = address;
}
public void SetOrder(Order order)=>Order = order;
public override Custome? ShallowClone()
{
return this.MemberwiseClone() as Custome;
}
public override Custome? DeepClone()
{
Custome custome = (Custome)MemberwiseClone();
custome.Address = new Address( Address?.Country ?? "China", Address?.City ?? "Beijing");
custome.Order = Order?.Clone();
return custome;
}
public void Show()
{
Console.WriteLine($"Name: {Name}");
Console.WriteLine($"Age: {Age}");
Console.WriteLine($"Address; {Address?.Country} ,{Address?.City}");
Console.WriteLine($"Order: \n \t at: { Order?.Time} " +
$"\t goods: {Order?.Goods.Aggregate((x, y) => x + " " + y)}");
}
}
ShallowClone函数没啥好说的了,直接调用MemberWiseClone完事。重点看DeepClone函数。
这里Adress和Order都是引用类型,其中Adresss是通过构造函数创建新对象,Order则不是,我们为Order实现了一个clone函数,专门用于创建对象。
internal class Order
{
public Guid Guid { get; set; }
public DateTime Time { get; set; }
public List<string> Goods { get; set; } = new List<string>();
public Order()
{
Guid = Guid.NewGuid();
Time = DateTime.Now;
}
public Order? Clone()
{
//不能直接返回对象o,yinwei Goods是引用类型,修改Goods会影响到克隆对象里面
var o= MemberwiseClone() as Order;
o.Goods=Goods.ToList();
return o;
}
public void AddGood(string item)=>Goods.Add(item);
}
(要注意Goods是引用类型,所以不能直接调用MemberWiseClone)
Address就没啥特别了:
internal class Address
{
public string Country { get; set; }
public string City { get; set; }
public Address(string country,string city)
{
Country = country;
City = city;
}
}
最后简单测试一下:
Address address = new Address("美国", "纽约");
Custome c1 = new Custome("川普", address);
Order Order = new Order();
Order.AddGood("Chip");
Order.AddGood("Gun");
c1.SetOrder(Order);
c1.Show();
var c2=c1.ShallowClone();
var c3=c1.DeepClone();
c2?.Show();
c3?.Show();
var o1 = Order.Clone();
c1?.Order?.AddGood("软中华");
c1.Age = 20;
address.City = "华盛顿";
c1.Show();
c2?.Show();
c3?.Show();
4.小结
4.1 优点
(1)、原型模式向客户隐藏了创建新实例的复杂性
(2)、原型模式允许动态增加或较少产品类。
(3)、原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
(4)、产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
4.2 缺点
(1)、每个类必须配备一个克隆方法
(2)、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
4.3 使用场景
1)、资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
(2)、性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
(3)、一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
完整代码:PatternDesignReview