類型: Creational
目的: 透過原型物件來複製出新物件
類別圖
使用情境
當原物件的建立過程繁瑣或是結構複雜,需要快速複製出新物件時,可使用原型模式來達到目的
以C#來說會先建立一個抽象類別,繼承 ICloneable 介面
//
// 摘要:
// Supports cloning, which creates a new instance of a class with the same value
// as an existing instance.
[ComVisible(true)]
public interface ICloneable
{
//
// 摘要:
// Creates a new object that is a copy of the current instance.
//
// 傳回:
// A new object that is a copy of this instance.
object Clone();
}
再由繼承該抽象類別的子類別,分別實作其 Clone 方法
範例
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace DesignPattern.Prototype
{
/// <summary>
/// 定義:
/// 使用原型實例指定創建對象的種類,然後通過拷貝這些原型來創建新的對象
///
/// 角色:
/// 1. Prototype:定義一個抽象方法 Clone [PrototypeBase]
/// 2. ConcretePrototype: 實現Clone方法 [ConcretePrototype1]
///
/// 缺點:
/// 1.當有大量子類別需要Clone時,實現Clone的子類別也要各寫一個
/// 2.Clone有分淺複製和深複製,.Net Framework提供的ICloneable 只有一個Clone方法
/// 無法明確定義出該Clone是淺複製還是深複製
/// </summary>
public class Prototype
{
public void Main()
{
ConcretePrototype1 obj = new ConcretePrototype1() { Id = 1, IdList = new List<int>() { 1, 2, 3 } };
ConcretePrototype1 copyObj = (ConcretePrototype1)obj.Clone();
obj.Id = 10;
obj.IdList.Clear();
Console.WriteLine($"obj={JsonConvert.SerializeObject(obj)}");
Console.WriteLine($"copyObj={JsonConvert.SerializeObject(copyObj)}");
}
}
/// <summary>
/// (Prototype)
/// </summary>
public abstract class PrototypeBase : ICloneable
{
// Methods
public abstract object Clone();
}
/// <summary>
/// (ConcretePrototype)
/// </summary>
public class ConcretePrototype1 : PrototypeBase
{
public int Id { get; set; }
public List<int> IdList { get; set; }
public override object Clone()
{
return this.MemberwiseClone();
}
}
}
可以觀察到 .Net 原生的 物件複製方法 MemberwiseClone 為淺複製,再複製參考型別的 Property 時並不會建立新的 Reference
進而導致修改參考型別的 Property 會連帶影響到被複製的物件
繼承 ICloneable 的缺點
在 MSDN 上有說明該 MemberwiseClone 為淺複製 Object.MemberwiseClone
而繼承自 ICloneable 的 Clone 方法,從外面直接引用時 並不知道這是深複製還是淺複製
也有人討論過該問題,但似乎沒有個結論 討論連結
我自己覺得這確實是會造成困擾,所以直接建立深複製與淺複製的方法,以免混淆
泛型深淺複製 Sample
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace DesignPattern.Generic_Prototype
{
/// <summary>
/// Prototype泛型改良版
///
/// 以泛型解決Prototype的缺點
/// 並把Clone定義分為深複製與淺複製以免誤用
/// </summary>
public class Generic_Prototype
{
public void Main()
{
TestClass a = new TestClass() { Id = 1, IdList = new List<int>() { 1, 2, 3 } };
TestClass shallowCopyObj = PrototypeHelper.ShallowCopy(a);
TestClass deepCopyObj = PrototypeHelper.DeepCopy(a);
a.Id = 10;
a.IdList.Clear();
Console.WriteLine($"a={JsonConvert.SerializeObject(a)}");
Console.WriteLine($"shallowCopyObj={JsonConvert.SerializeObject(shallowCopyObj)}");
Console.WriteLine($"deepCopyObj={JsonConvert.SerializeObject(deepCopyObj)}");
}
}
[Serializable]
internal class TestClass
{
public int Id { get; set; }
public List<int> IdList { get; set; }
}
public static class PrototypeHelper
{
// 淺複製
public static T ShallowCopy<T>(this T targetObject)
{
if ((object)targetObject is null)
throw new ArgumentNullException();
return (T)targetObject.GetType().GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(targetObject, null);
}
// 深複製
public static T DeepCopy<T>(this T targetObject)
{
using (var memory = new System.IO.MemoryStream())
{
System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
formatter.Serialize(memory, targetObject);
memory.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(memory);
}
}
}
}
轉載請註明來源,若有任何錯誤或表達不清楚的地方,歡迎在下方評論區留言,也可以來信至 leozheng0621@gmail.com
如果文章對您有幫助,歡迎斗內(donate),請我喝杯咖啡