首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 設計模式深度解析:原型模式與淺/深拷貝完整指南

     編輯:關於C#

前言

原型模式 是創建型設計模式之一,核心思想:**以原型實例為基礎,透過複製 (克隆) 方式建立新物件,而非透過類別實例化**。適用於物件建立成本高、構造函數複雜、或需動態增減產品類別的場景。C# 透過 `ICloneable` 介面與 `MemberwiseClone()` 方法提供原生支援,關鍵在於正確理解 **淺拷貝** 與 **深拷貝** 的差異。

模式結構與角色

  • Prototype (抽象原型):定義 `Clone()` 介面,通常繼承 `ICloneable`
  • ConcretePrototype (具體原型):實作 `Clone()`,決定淺/深拷貝策略
  • Client (客戶端):透過呼叫 `Clone()` 取得新物件
  • PrototypeManager (原型管理器,可選):登記、管理、檢索原型實例

標準實作:淺拷貝


using System;

// 抽象原型
public abstract class Prototype : ICloneable
{
    protected string Id { get; }

    protected Prototype(string id) => Id = id;

    public abstract object Clone(); // 回傳 object 符合 ICloneable
}

// 具體原型 1
public class ConcretePrototype1 : Prototype
{
    public ConcretePrototype1(string id) : base(id) { }

    public override object Clone()
    {
        // MemberwiseClone() 執行淺拷貝:複製值型別、複製參考型別的「參考」
        return this.MemberwiseClone();
    }
}

// 具體原型 2
public class ConcretePrototype2 : Prototype
{
    private readonly List _tags = new(); // 參考型別欄位

    public ConcretePrototype2(string id) : base(id) { }

    public void AddTag(string tag) => _tags.Add(tag);
    public IReadOnlyList Tags => _tags.AsReadOnly();

    public override object Clone()
    {
        // ⚠️ 淺拷貝:_tags 列表參考被複製,原物件與複製物件共享同一個列表!
        return this.MemberwiseClone();
    }
}

// 客戶端測試淺拷貝行為
var p1 = new ConcretePrototype1("I");
var c1 = (ConcretePrototype1)p1.Clone();
Console.WriteLine($"Cloned: {c1.Id}"); // 輸出: Cloned: I

var p2 = new ConcretePrototype2("II");
p2.AddTag("tag1");
p2.AddTag("tag2");

var c2 = (ConcretePrototype2)p2.Clone();
// 修改複製物件的標籤
c2.AddTag("tag3");

Console.WriteLine($"Original tags: {string.Join(", ", p2.Tags)}"); // 輸出: tag1, tag2, tag3 (受影響!)
Console.WriteLine($"Cloned tags: {string.Join(", ", c2.Tags)}");   // 輸出: tag1, tag2, tag3 (同一個列表)

深拷貝實作策略

策略 1:手動複製 (效能最好、控制最精確)


public class DeepCopyPrototype : ICloneable
{
    public int[] Numbers { get; set; } = { 1, 2, 3 };
    public List Tags { get; set; } = new();
    public Address Address { get; set; } // 巢狀參考型別

    public object Clone()
    {
        var clone = (DeepCopyPrototype)this.MemberwiseClone(); // 先淺拷貝
        
        // 手動深拷貝每個參考型別欄位
        clone.Numbers = (int[])Numbers.Clone(); // 陣列支援 Clone()
        clone.Tags = new List(Tags);    // List 複製建構子
        clone.Address = Address?.Clone() as Address; // 遞迴深拷貝巢狀物件
        
        return clone;
    }
}

public class Address : ICloneable
{
    public string City { get; set; }
    public string Street { get; set; }

    public object Clone() => this.MemberwiseClone(); // 純值型別/字串(不可變)淺拷貝即深拷貝
}

// 測試
var original = new DeepCopyPrototype
{
    Numbers = new[] { 1, 2, 3 },
    Tags = new List { "A", "B" },
    Address = new Address { City = "Taipei", Street = "Zhongxiao Rd" }
};

var cloned = (DeepCopyPrototype)original.Clone();
cloned.Numbers[0] = 99;
cloned.Tags.Add("C");
cloned.Address.City = "Kaohsiung";

Console.WriteLine($"Original: {string.Join(",", original.Numbers)}, {string.Join(",", original.Tags)}, {original.Address.City}");
// 輸出: 1,2,3, A,B, Taipei (未受影響)
Console.WriteLine($"Cloned: {string.Join(",", cloned.Numbers)}, {string.Join(",", cloned.Tags)}, {cloned.Address.City}");
// 輸出: 99,2,3, A,B,C, Kaohsiung

策略 2:序列化反序列化 (通用、簡單、效能較差)


using System.Text.Json;

public class SerializablePrototype : ICloneable
{
    public int[] Numbers { get; set; }
    public List Tags { get; set; }
    public Address Address { get; set; }

    public object Clone()
    {
        // 使用 JSON 序列化深拷貝
        string json = JsonSerializer.Serialize(this);
        return JsonSerializer.Deserialize(json);
    }
}

// 注意:需要無參數建構子、屬性可寫入、循環參考會拋例外
// 可改用 BinaryFormatter (舊版)、protobuf-net、MessagePack 等二進位格式提升效能

策略 3:Expression 樹編譯 (高效能通用深拷貝)


using System.Linq.Expressions;

public static class DeepCloner
{
    private static readonly Dictionary> _cloners = new();

    public static T DeepClone(T source) where T : class
    {
        if (source == null) return null;
        
        var type = typeof(T);
        if (!_cloners.TryGetValue(type, out var cloner))
        {
            cloner = CompileCloner(type);
            _cloners[type] = cloner;
        }
        return (T)cloner(source);
    }

    private static Func CompileCloner(Type type)
    {
        var param = Expression.Parameter(typeof(object), "source");
        var cast = Expression.Convert(param, type);
        
        // 建立 MemberInitExpression 複製所有屬性
        var bindings = type.GetProperties()
            .Where(p => p.CanRead && p.CanWrite)
            .Select(p => Expression.Bind(p, Expression.Property(cast, p)))
            .Cast();
        
        var newExpr = Expression.MemberInit(Expression.New(type), bindings);
        var lambda = Expression.Lambda>(newExpr, param);
        return lambda.Compile();
    }
}

// 使用:var cloned = DeepCloner.DeepClone(original);

帶原型管理器的原型模式


// 顏色原型
public abstract class ColorPrototype : ICloneable
{
    public abstract object Clone();
}

public class Color : ColorPrototype
{
    public int Red { get; }
    public int Green { get; }
    public int Blue { get; }

    public Color(int r, int g, int b) => (Red, Green, Blue) = (r, g, b);

    public override object Clone() => this.MemberwiseClone(); // 值型別欄位,淺即深

    public void Display() => Console.WriteLine($"RGB: {Red}, {Green}, {Blue}");
}

// 原型管理器
public class ColorManager
{
    private readonly Dictionary _prototypes = new();

    public ColorPrototype this[string name]
    {
        get => _prototypes.TryGetValue(name, out var p) ? p : null;
        set => _prototypes[name] = value;
    }
}

// 應用:使用者自訂顏色主題
var manager = new ColorManager();

// 系統預設色
manager["Red"] = new Color(255, 0, 0);
manager["Green"] = new Color(0, 255, 0);
manager["Blue"] = new Color(0, 0, 255);

// 使用者自訂色 (克隆自預設色再微調)
manager["Angry"] = new Color(255, 54, 0);
manager["Peace"] = new Color(128, 211, 128);
manager["Flame"] = new Color(211, 34, 20);

// 客戶端使用:克隆後可安全修改
Color c1 = (Color)manager["Red"].Clone();
c1.Display(); // RGB: 255, 0, 0

Color c2 = (Color)manager["Peace"].Clone();
c2.Display(); // RGB: 128, 211, 128

淺拷貝 vs 深拷貝完整比較

特性淺拷貝深拷貝
實作方式MemberwiseClone()手動複製 / 序列化 / Expression
值型別欄位複製數值複製數值
字串欄位複製參考 (安全,字串不可變)複製參考 (安全)
陣列/集合欄位複製參考 (共享同一集合)建立新集合並複製元素
巢狀物件欄位複製參考 (共享同一物件)遞迴深拷貝巢狀物件
修改複製物件影響原物件是 (參考型別欄位)否 (完全獨立)
效能極快 (單指令)較慢 (配置新記憶體、複製資料)
適用場景全值型別、不可變參考型別、刻意共享可變參考型別、需要完全隔離

ICloneable 介面爭議與最佳實務

Microsoft 官方文件建議:**公開 API 避免使用 `ICloneable`**,因為它不強制淺/深拷貝語意,導致呼叫端無法預期行為。

推薦替代方案


// 方案 1:明確命名的複製方法
public class Document
{
    public Document DeepClone() { ... } // 語意清晰
    public Document ShallowClone() { ... }
}

// 方案 2:複製建構子 (Copy Constructor)
public class Document
{
    public Document(Document other) // 明確為深拷貝
    {
        // 複製邏輯
    }
}

// 方案 3:Record 型別 (C# 9+) 自動提供 with 運算式
public record Document(string Title, List Tags)
{
    // 編譯器自動產生複製建構子
    // 使用: var copy = original with { Title = "New Title" }; // 淺拷貝 Tags
    // 深拷貝需自行實作: var copy = original with { Tags = new List(original.Tags) };
}

適用性判斷

考慮使用原型模式當:

  • ✅ 物件建立成本高 (複雜計算、資料庫查詢、IO 操作)
  • ✅ 系統需動態增減產品類別 (執行期註冊新原型)
  • ✅ 避免建立與產品類別平行的工廠類別階層
  • ✅ 物件狀態組合較固定,適合複製後微調
  • ❌ 物件包含不可序列化資源 (資料庫連線、檔案控制代碼、執行緒)
  • ❌ 物件圖極深或含循環參考 (深拷貝極其複雜)

.NET 生態系中的原型應用

場景實作說明
物件複製ICloneable, MemberwiseClone基礎支援
資料傳輸物件Record with 運算式C# 9+ 語法糖,淺拷貝
Entity FrameworkEntity.Clone()實體脫離追蹤建立新實體
WPF FreezableClone(), CloneCurrentValue()凍結物件的深拷貝
ASP.NET Core OptionsIOptionsSnapshot每請求複製選項實例

總結

原型模式透過「複製」替代「建構」,在特定場景下極具優勢。C# 開發者必須精通:

  1. 淺拷貝 vs 深拷貝:`MemberwiseClone()` 只複製參考,參考型別欄位會共享;深拷貝需手動或序列化遞迴複製
  2. ICloneable 的模糊語意:公開 API 建議使用明確命名方法 (`DeepClone`、複製建構子、Record `with`)
  3. 原型管理器:支援執行期動態註冊/檢索原型,實現「原型登記表」模式
  4. 效能權衡:淺拷貝極快,深拷貝有成本;大量物件複製時考慮 `struct`、物件池、序列化優化

掌握這些核心概念,能在需要動態物件建立、狀態快照、撤銷重做、物件池化等場景中靈活運用原型模式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved