首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

原型模式深度解析:克隆羊多利的 Java 實現、淺/深拷貝陷阱與管理器模式

     編輯:Java 綜合教學

前言:為什麼需要原型模式?

當創建對象成本高昂 (如大對象、需查詢數據庫、複雜計算) 或系統需動態增減產品類時,原型模式 通過複製現有原型對象 (clone()) 來創建新實例,避免繁瑣的構造過程。本文剖析簡單/登記兩種形式、Cloneable 機制、淺/深拷貝本質區別、Externalizable 繞過 transient 的特例,並給出最佳實踐。

一、核心角色與 UML

角色職責
Client (客戶)請求複製原型
Prototype (抽象原型)聲明 clone() 接口
ConcretePrototype (具體原型)實現 clone(),支持複製
PrototypeManager (原型管理器)登記形式專用:緩存原型、按鍵獲取

二、簡單形式:直接複製

// 1. 抽象原型
interface Prototype extends Cloneable {
    Prototype clone() throws CloneNotSupportedException;
}

// 2. 具體原型
class ConcretePrototype implements Prototype {
    private String field;
    public ConcretePrototype(String field) { this.field = field; }
    @Override public Prototype clone() {
        try { return (Prototype) super.clone(); } // Object.clone() 是淺拷貝
        catch (CloneNotSupportedException e) { throw new AssertionError(); }
    }
}

// 3. 客戶端
class Client {
    public void operation(Prototype prototype) {
        Prototype copy = prototype.clone(); // 直接複製
        // 使用 copy...
    }
}

適用:原型種類少、固定、無需集中管理。

三、登記形式:原型管理器集中緩存

class PrototypeManager {
    private Map registry = new HashMap<>();

    public void register(String key, Prototype prototype) {
        registry.put(key, prototype);
    }

    public Prototype get(String key) {
        return registry.get(key).clone(); // 返回複製品,保護原型
    }
}

// 客戶端
Client client = new Client();
PrototypeManager pm = new PrototypeManager();
pm.register("typeA", new ConcretePrototype("A"));
Prototype p = pm.get("typeA"); // 獲取克隆實例

適用:原型動態增減、運行時決定類型、需集中控制實例創建。

四、Java Clone 機制與淺/深拷貝本質

1. Object.clone() 默認行為:淺拷貝

  • 基本類型:直接複製值。
  • 引用類型:複製引用地址,原對象與克隆對象共享同一個堆對象。
class Address { String city; }
class User implements Cloneable {
    String name; Address addr;
    @Override protected User clone() {
        try { return (User) super.clone(); } catch (Exception e) { throw new Error(e); }
    }
}

User u1 = new User(); u1.name = "Tom"; u1.addr = new Address(); u1.addr.city = "Beijing";
User u2 = u1.clone();
u2.name = "Jerry";           // 基本類型獨立
u2.addr.city = "Shanghai";   // 引用類型共享!
System.out.println(u1.addr.city); // Shanghai ← 原對象被修改!

2. 深拷貝三種實現方式

方式原理優點缺點
重寫 clone 遞歸複製 在 clone() 中手動 new 所有引用字段 可控、無依賴 代碼繁瑣、易漏字段、循環引用需特殊處理
序列化/反序列化 實現 Serializable,寫入字節流再讀出 自動處理整個對象圖、支持循環引用 性能較差、需所有類 Serializable、transient 字段會丟失
第三方庫 (推薦) Apache Commons Lang SerializationUtils.clone() 或 JSON 轉換 (Gson/Jackson) 一行代碼、JSON 可跨語言、性能尚可 引入依賴、JSON 需無參構造器
// 方式 1:手動深拷貝
@Override protected User clone() {
    User copy = (User) super.clone();
    copy.addr = new Address(this.addr.city); // 手動複製 Address
    return copy;
}

// 方式 2:序列化深拷貝 (工具類)
public static  T deepClone(T obj) {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(bos)) {
        oos.writeObject(obj);
        try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            return (T) ois.readObject();
        }
    } catch (Exception e) { throw new RuntimeException(e); }
}

// 方式 3:JSON 深拷貝 (Gson)
User u2 = new Gson().fromJson(new Gson().toJson(u1), User.class);

五、特例:Externalizable 繞過 transient

實現 Externalizable 時,序列化完全由 writeExternal/readExternal 手動控制,transient 失效:

class ExternalizableDemo implements Externalizable {
    private transient String content = "我被 transient 修飾";
    @Override public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(content); // 手動寫入,照樣序列化!
    }
    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        content = (String) in.readObject();
    }
}
// 輸出: "我被 transient 修飾"

結論:Externalizable 給予完全控制權,transient 僅對默認 Serializable 機制生效。

六、優缺點總結

優點缺點
動態增減產品類,無需修改代碼 每個類都需實現 clone(),侵入性強
簡化創建結構,無需工廠層級結構 深拷貝複雜,循環引用、transient 字段易出錯
支持運行時動態加載新功能 構造函數不會被調用,可能跳過初始化邏輯
無需預先確定類層次 克隆對象可能攜帶不該共享的資源 (連接、鎖、線程)

七、最佳實踐建議

  1. 優先考慮構造器/工廠/建造者模式,只有明確「複製比新建快/簡單」時用原型。
  2. 實現 Cloneable 並重寫 clone() 為 public,拋出 CloneNotSupportedException 改為運行時異常。
  3. 深拷貝首選 JSON/序列化工具類,手動遞歸僅用於極簡對象。
  4. 不可變對象無需克隆,直接共享引用 (如 String、包裝類、Guava 不可變集合)。
  5. 文檔化拷貝深度:類註明「淺拷貝/深拷貝」,避免調用方誤用。

八、總結

原型模式 = Cloneable + clone() + (可選) 管理器。核心難點在於淺/深拷貝選擇:基本類型/不可變對象淺拷貝無傷大雅,可變引用對象必須深拷貝。現代 Java 開發中,JSON 序列化深拷貝 是性能與開發效率的最佳平衡點。記住:克隆是手段,解決創建成本高/動態類型才是目的。

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