模式設計之創建對象:原型模式(Prototype)詳解

引子:

在有些系統中,存在大量相同或相似對象的創建問題,如果用傳統的構造函數來創建對象,會比較複雜且耗時耗資源,用原型模式生成對象就很高效,就像孫悟空拔下猴毛輕輕一吹就變出很多孫悟空一樣簡單。

原型模式的定義與特點

原型(Prototype)模式的定義如下:用一個已經創建的實例作爲原型,通過複製該原型對象來創建一個和原型相同或相似的新對象。在這裏,原型實例指定了要創建的對象的種類。用這種方式創建對象非常高效,根本無須知道對象創建的細節。例如,Windows 操作系統的安裝通常較耗時,如果複製就快了很多。在生活中複製的例子非常多,這裏不一一列舉了。

原型模式的優點:

  • Java 自帶的原型模式基於內存二進制流的複製,在性能上比直接 new 一個對象更加優良。
  • 可以使用深克隆方式保存對象的狀態,使用原型模式將對象複製一份,並將其狀態保存起來,簡化了創建對象的過程,以便在需要的時候使用(例如恢復到歷史某一狀態),可輔助實現撤銷操作。

原型模式的缺點:

  • 需要爲每一個類都配置一個 clone 方法
  • clone 方法位於類的內部,當對已有類進行改造的時候,需要修改代碼,違背了開閉原則。
  • 當實現深克隆時,需要編寫較爲複雜的代碼,而且當對象之間存在多重嵌套引用時,爲了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來會比較麻煩。因此,深克隆、淺克隆需要運用得當。

原型模式的結構與實現

由於 Java 提供了對象的 clone() 方法,所以用 Java 實現原型模式很簡單。

1. 模式的結構

原型模式包含以下主要角色。

  1. 抽象原型類:規定了具體原型對象必須實現的接口。
  2. 具體原型類:實現抽象原型類的 clone() 方法,它是可被複制的對象。
  3. 訪問類:使用具體原型類中的 clone() 方法來複制新的對象。


其結構圖如圖 1 所示。

                                                    圖1 原型模式的結構圖

2. 模式的實現

原型模式的克隆分爲淺克隆深克隆

  • 淺克隆:創建一個新對象,新對象的屬性和原來對象完全相同,對於非基本類型屬性,仍指向原有屬性所指向的對象的內存地址。
  • 深克隆:創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。


Java 中的 Object 類提供了淺克隆的 clone() 方法,具體原型類只要實現 Cloneable 接口就可實現對象的淺克隆,這裏的 Cloneable 接口就是抽象原型類。其代碼如下:

//具體原型類
class Realizetype implements Cloneable {
    Realizetype() {
        System.out.println("具體原型創建成功!");
    }

    public Object clone() throws CloneNotSupportedException {
        System.out.println("具體原型複製成功!");
        return (Realizetype) super.clone();
    }
}

//原型模式的測試類
public class PrototypeTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Realizetype obj1 = new Realizetype();
        Realizetype obj2 = (Realizetype) obj1.clone();
        System.out.println("obj1==obj2?" + (obj1 == obj2));
    }
}

運行結果:

具體原型創建成功!
具體原型複製成功!
obj1==obj2?false

原型模式的應用實例

【例1】用原型模式模擬“孫悟空”複製自己。

分析:孫悟空拔下猴毛輕輕一吹就變出很多孫悟空,這實際上是用到了原型模式。這裏的孫悟空類 SunWukong 是具體原型類,而 Java 中的 Cloneable 接口是抽象原型類。

同前面介紹的豬八戒實例一樣,由於要顯示孫悟空的圖像(點擊此處下載該程序所要顯示的孫悟空的圖片),所以將孫悟空類定義成面板 JPanel 的子類,裏面包含了標籤,用於保存孫悟空的圖像。

另外,重寫了 Cloneable 接口的 clone() 方法,用於複製新的孫悟空。訪問類可以通過調用孫悟空的 clone() 方法複製多個孫悟空,並在框架窗體 JFrame 中顯示。圖 2 所示是其結構圖。

                                     圖2 孫悟空生成器的結構圖


程序代碼如下:

import java.awt.*;
import javax.swing.*;

class SunWukong extends JPanel implements Cloneable {
    private static final long serialVersionUID = 5543049531872119328L;

    public SunWukong() {
        JLabel l1 = new JLabel(new ImageIcon("src/Wukong.jpg"));
        this.add(l1);
    }

    public Object clone() {
        SunWukong w = null;
        try {
            w = (SunWukong) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("拷貝悟空失敗!");
        }
        return w;
    }
}

public class ProtoTypeWukong {
    public static void main(String[] args) {
        JFrame jf = new JFrame("原型模式測試");
        jf.setLayout(new GridLayout(1, 2));
        Container contentPane = jf.getContentPane();
        SunWukong obj1 = new SunWukong();
        contentPane.add(obj1);
        SunWukong obj2 = (SunWukong) obj1.clone();
        contentPane.add(obj2);
        jf.pack();
        jf.setVisible(true);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

程序的運行結果如圖 3 所示。

圖3 孫悟空克隆器的運行結果


用原型模式除了可以生成相同的對象,還可以生成相似的對象,請看以下實例。

【例2】用原型模式生成“三好學生”獎狀。

分析:同一學校的“三好學生”獎狀除了獲獎人姓名不同,其他都相同,屬於相似對象的複製,同樣可以用原型模式創建,然後再做簡單修改就可以了。圖 4 所示是三好學生獎狀生成器的結構圖。

圖4 獎狀生成器的結構圖


程序代碼如下:

public class ProtoTypeCitation {
    public static void main(String[] args) throws CloneNotSupportedException {
        citation obj1 = new citation("張三", "同學:在2016學年第一學期中表現優秀,被評爲三好學生。", "韶關學院");
        obj1.display();
        citation obj2 = (citation) obj1.clone();
        obj2.setName("李四");
        obj2.display();
    }
}

//獎狀類
class citation implements Cloneable {
    String name;
    String info;
    String college;

    citation(String name, String info, String college) {
        this.name = name;
        this.info = info;
        this.college = college;
        System.out.println("獎狀創建成功!");
    }

    void setName(String name) {
        this.name = name;
    }

    String getName() {
        return (this.name);
    }

    void display() {
        System.out.println(name + info + college);
    }

    public Object clone() throws CloneNotSupportedException {
        System.out.println("獎狀拷貝成功!");
        return (citation) super.clone();
    }
}

運行結果:

獎狀創建成功!
張三同學:在2016學年第一學期中表現優秀,被評爲三好學生。韶關學院
獎狀拷貝成功!
李四同學:在2016學年第一學期中表現優秀,被評爲三好學生。韶關學院

原型模式的應用場景

原型模式通常適用於以下場景。

  • 對象之間相同或相似,即只是個別的幾個屬性不同的時候。
  • 創建對象成本較大,例如初始化時間長,佔用CPU太多,或者佔用網絡資源太多等,需要優化資源。
  • 創建一個對象需要繁瑣的數據準備或訪問權限等,需要提高性能或者提高安全性。
  • 系統中大量使用該類對象,且各個調用者都需要給它的屬性重新賦值。


Spring 中,原型模式應用的非常廣泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的具體應用。

原型模式的擴展

原型模式可擴展爲帶原型管理器的原型模式,它在原型模式的基礎上增加了一個原型管理器 PrototypeManager 類。該類用 HashMap 保存多個複製的原型,Client 類可以通過管理器的 get(String id) 方法從中獲取複製的原型。其結構圖如圖 5 所示。

圖5 帶原型管理器的原型模式的結構圖


【例3】用帶原型管理器的原型模式來生成包含“圓”和“正方形”等圖形的原型,並計算其面積。分析:本實例中由於存在不同的圖形類,例如,“圓”和“正方形”,它們計算面積的方法不一樣,所以需要用一個原型管理器來管理它們,圖 6 所示是其結構圖。

圖6 圖形生成器的結構圖


程序代碼如下:

import java.util.*;

interface Shape extends Cloneable {
    public Object clone();    //拷貝

    public void countArea();    //計算面積
}

class Circle implements Shape {
    public Object clone() {
        Circle w = null;
        try {
            w = (Circle) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("拷貝圓失敗!");
        }
        return w;
    }

    public void countArea() {
        int r = 0;
        System.out.print("這是一個圓,請輸入圓的半徑:");
        Scanner input = new Scanner(System.in);
        r = input.nextInt();
        System.out.println("該圓的面積=" + 3.1415 * r * r + "\n");
    }
}

class Square implements Shape {
    public Object clone() {
        Square b = null;
        try {
            b = (Square) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("拷貝正方形失敗!");
        }
        return b;
    }

    public void countArea() {
        int a = 0;
        System.out.print("這是一個正方形,請輸入它的邊長:");
        Scanner input = new Scanner(System.in);
        a = input.nextInt();
        System.out.println("該正方形的面積=" + a * a + "\n");
    }
}

class ProtoTypeManager {
    private HashMap<String, Shape> ht = new HashMap<String, Shape>();

    public ProtoTypeManager() {
        ht.put("Circle", new Circle());
        ht.put("Square", new Square());
    }

    public void addshape(String key, Shape obj) {
        ht.put(key, obj);
    }

    public Shape getShape(String key) {
        Shape temp = ht.get(key);
        return (Shape) temp.clone();
    }
}

public class ProtoTypeShape {
    public static void main(String[] args) {
        ProtoTypeManager pm = new ProtoTypeManager();
        Shape obj1 = (Circle) pm.getShape("Circle");
        obj1.countArea();
        Shape obj2 = (Shape) pm.getShape("Square");
        obj2.countArea();
    }
}

運行結果:

這是一個圓,請輸入圓的半徑:3
該圓的面積=28.2735

這是一個正方形,請輸入它的邊長:3
該正方形的面積=9

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章