引子:
在有些系統中,存在大量相同或相似對象的創建問題,如果用傳統的構造函數來創建對象,會比較複雜且耗時耗資源,用原型模式生成對象就很高效,就像孫悟空拔下猴毛輕輕一吹就變出很多孫悟空一樣簡單。
原型模式的定義與特點
原型(Prototype)模式的定義如下:用一個已經創建的實例作爲原型,通過複製該原型對象來創建一個和原型相同或相似的新對象。在這裏,原型實例指定了要創建的對象的種類。用這種方式創建對象非常高效,根本無須知道對象創建的細節。例如,Windows 操作系統的安裝通常較耗時,如果複製就快了很多。在生活中複製的例子非常多,這裏不一一列舉了。
原型模式的優點:
- Java 自帶的原型模式基於內存二進制流的複製,在性能上比直接 new 一個對象更加優良。
- 可以使用深克隆方式保存對象的狀態,使用原型模式將對象複製一份,並將其狀態保存起來,簡化了創建對象的過程,以便在需要的時候使用(例如恢復到歷史某一狀態),可輔助實現撤銷操作。
原型模式的缺點:
- 需要爲每一個類都配置一個 clone 方法
- clone 方法位於類的內部,當對已有類進行改造的時候,需要修改代碼,違背了開閉原則。
- 當實現深克隆時,需要編寫較爲複雜的代碼,而且當對象之間存在多重嵌套引用時,爲了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來會比較麻煩。因此,深克隆、淺克隆需要運用得當。
原型模式的結構與實現
由於 Java 提供了對象的 clone() 方法,所以用 Java 實現原型模式很簡單。
1. 模式的結構
原型模式包含以下主要角色。
- 抽象原型類:規定了具體原型對象必須實現的接口。
- 具體原型類:實現抽象原型類的 clone() 方法,它是可被複制的對象。
- 訪問類:使用具體原型類中的 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 所示。
用原型模式除了可以生成相同的對象,還可以生成相似的對象,請看以下實例。
【例2】用原型模式生成“三好學生”獎狀。
分析:同一學校的“三好學生”獎狀除了獲獎人姓名不同,其他都相同,屬於相似對象的複製,同樣可以用原型模式創建,然後再做簡單修改就可以了。圖 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 所示。
【例3】用帶原型管理器的原型模式來生成包含“圓”和“正方形”等圖形的原型,並計算其面積。分析:本實例中由於存在不同的圖形類,例如,“圓”和“正方形”,它們計算面積的方法不一樣,所以需要用一個原型管理器來管理它們,圖 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