一 原型模式介紹
在面向對象系統中,使用原型模式來複制一個對象自身,從而克隆出多個與原型對象一模一樣的對象。
另外在軟件系統中,有些對象的創建過程較爲複雜,而且有時候需要頻繁創建,原型模式通過給出一個原型對象來指明所要創建的對象的類型,然後用複製這個原型對象的辦法創建出更多同類型的對象,這就是原型模式的意圖所在。
1.1 定義
使用原型實例指定將要創建的對象類型,通過複製這個實例創建新的對象。
1.2 原型模式適用場景
我們現在一般會使用new關鍵字指定類名生成類的實例(PS:我們以前使用java.lang.Cloneable
的一個很大原因是使用new創建對象的速度相對來說會慢一些,隨着JVM性能的提升,new的速度和Object的clone()方法
的速度差不多了。)。
使用new關鍵字創建類的時候必須指定類名,但是在開發過程中也會有“在不指定類名的前提下生成實例”的需求。例如,在下面這些情況下,就需要根據現有的實例來生成新的實例。
1) 對象種類繁多,無法將他們整合到一個類的時候;
2) 難以根據類生成實例時;
3) 想解耦框架與生成的實例時。
如果想要讓生成實例的框架不再依賴於具體的類,這時,不能指定類名來生成實例,而要事先“註冊”一個“原型”實例,然後通過複製該實例來生成新的實例。
1.3 模式分析
在原型模式結構中定義了一個抽象原型類,所有的Java類都繼承自java.lang.Object
,而Object類提供一個clone()方法
,可以將一個Java對象複製一份。因此在Java中可以直接使用Object提供的clone()方法來實現對象的克隆,Java語言中的原型模式實現很簡單。
能夠實現克隆的Java類必須實現一個標識接口Cloneable
,表示這個Java類支持複製。如果一個類沒有實現這個接口但是調用了clone()方法
,Java編譯器將拋出一個CloneNotSupportedException
異常。
注意: `java.lang.Cloneable
只是起到告訴程序可以調用clone方法的作用,它本身並沒有定義任何方法。
在使用原型模式克隆對象時,根據其成員對象是否也克隆,原型模式可以分爲兩種形式:深克隆 和 淺克隆 。
1.4 模式優缺點分析
原型模式的優點:
- 當創建新的對象實例較爲複雜時,使用原型模式可以簡化對象的創建過程,通過一個已有實例可以提高新實例的創建效率。
- 可以動態增加或減少產品類。
- 原型模式提供了簡化的創建結構。
- 可以使用深克隆的方式保存對象的狀態。
原型模式的缺點:
- 需要爲每一個類配備一個克隆方法,而且這個克隆方法需要對類的功能進行通盤考慮,這對全新的類來說不是很難,但對已有的類進行改造時,不一定是件容易的事,必須修改其源代碼,違背了“開閉原則”。
- 在實現深克隆時需要編寫較爲複雜的代碼。
二 示例程序
下面示例程序的作用是將字符串放入方框中顯示出來或者是加了下劃線顯示出來。
類和接口一覽表:
示例程序類圖:
2.1 Product接口 (Prototype)
Product接口
是複製功能接口,該接口繼承了java.lang.Cloneable
(只有實現了該接口的類的實例纔可以調用clone()方法
複製實例,否則會拋出異常).
另外需要注意:`java.lang.Cloneable
只是起到告訴程序可以調用clone方法的作用,它本身並沒有定義任
public interface Product extends Cloneable{
//use方法是用於“使用”的方法,具體怎麼“使用”,則被交給子類去實現。
public abstract void use(String s);
//creatClone方法是用於複製實例的方法
public abstract Product creatClone();
}
2.2 Manager類(Client)
Manager類使用Product接口來複制實例。
Product接口
以及Manager類
的代碼完全沒有出現在MessageBox類
和UnderlinePen類
的名字,因此這意味着我們可以獨立地修改Product接口
以及Manager類
,不受MessageBox類
和UnderlinePen類
的影響。這是非常重要的,因爲 一旦在類中使用到了別的類名,就意味着該類與其他類緊密的地耦合在了一起 。在Manager類
中,並沒有寫明具體的類名, 僅僅使用了Product
這個接口名。也就是說,Product接口
成爲了連接Manager類
與其他具體類之間的橋樑。
import java.util.HashMap;
public class Manager {
//保存實例的“名字”和“實例”之間的對應關係
private HashMap<String, Product> showcase=new HashMap<String, Product>();
//register方法將接收到的一組“名字”和“Product接口”註冊到showcase中。這裏Product是實現Product接口的實例,具體還未確定
public void register(String name ,Product product){
showcase.put(name, product);
}
public Product create(String productname){
Product p=showcase.get(productname);
return p.creatClone();
}
}
2.3 MessageBox類(ConcreteProtorype)
裝飾方框樣式的具體原型,實現了Product接口
,實現複製現有實例並生成新實例的方法。
public class MessageBox implements Product {
//保存的是裝飾方框使用的字符樣式
private char decochar;
public MessageBox(char decochar) {
this.decochar = decochar;
}
@Override
public void use(String s) {
int length=s.getBytes().length;
for (int i = 0; i < length+4; i++) {
System.out.print(decochar);
}
System.out.println("");
System.out.println(decochar+" "+s+" "+decochar);
for (int i = 0; i < length+4; i++) {
System.out.print(decochar);
}
System.out.println("");
}
//該方法用於複製自己
@Override
public Product creatClone() {
Product p=null;
try {
p=(Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
只有類自己(或是它的子類)能夠調用Java語言中定義的clone方法。當其他類要求複製實例時,必須先調用createClone這樣的方法,然後在該方法內部在調用clone方法。
2.4 UnderlinePen類(ConcreteProtorype)
下劃線樣式的具體原型,實現了Product接口
,用於實現複製現有實例並生成新實例的方法。UnderlinePen類
的實現幾乎和MessageBox類
一樣,不同的可能只是use方法
的實現。
public class UnderlinePen implements Product {
private char ulchar;
public UnderlinePen(char ulchar) {
this.ulchar = ulchar;
}
@Override
public void use(String s) {
int length = s.getBytes().length;
System.out.println("\""+s+"\"");
for (int i = 0; i <length+2; i++) {
System.out.print(ulchar);
}
System.out.println("");
}
@Override
public Product creatClone() {
Product p=null;
try {
p=(Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
2.5 Main類
Main類
首先生成Manager實例。接着,在Manager實例
中通過`register方法
註冊了UnderlinePen類
的實例(帶名字)和MessageBox類
的實例(帶名字)。
public class Main {
public static void main(String[] args) {
Manager manager = new Manager();
UnderlinePen underlinePen=new UnderlinePen('~');
MessageBox mbox=new MessageBox('*');
MessageBox sbox=new MessageBox('/');
manager.register("Strong message", underlinePen);
manager.register("Waring Box", mbox);
manager.register("Slash Box", sbox);
Product p1=manager.create("Strong message");
p1.use("hello world");
Product p2=manager.create("Waring Box");
p2.use("hello world");
Product p3=manager.create("Slash Box");
p3.use("hello world");
}
}
運行結果:
三 原型模式的角色分析
通過上面的例子,相信大家對於原型模式有了更進一步的認識,下面我們看看原型模式的幾個登場角色。
3.1 Prototype(抽象原型類)
Product角色負責定義用於複製現有實例來生成新實例的方法。在示例程序中的Product接口就是該角色。
3.2 ConcretePrototype(具體原型類)
ConcretePrototype角色負責實現複製現有實例並生成新實例的方法。在示例程序中,MessageBox和UnderlinePen都是該角色。
3.3 Client(客戶類/使用者)
Client角色負責使用複製實例的方法生成新的實例。在示例程序中,Manager類扮演的就是該角色。
Prototype模式的類圖:
四 原型模式的實際應用案例
(1) 原型模式應用於很多軟件中,如果每次創建一個對象要花大量時間,原型模式是最好的解決方案。很多軟件提供的複製(Ctrl + C)
和粘貼(Ctrl + V)
操作就是原型模式的應用,複製得到的對象與原型對象是兩個類型相同但內存地址不同的對象,通過原型模式可以大大提高對象的創建效率。
(2) 在Struts2
中爲了保證線程的安全性,Action對象
的創建使用了原型模式,訪問一個已經存在的`Action對象
時將通過克隆的方式創建出一個新的對象,從而保證其中定義的變量無須進行加鎖實現同步,每一個Action
中都有自己的成員變量,避免Struts1
因使用單例模式而導致的併發和同步問題。
(3) 在Spring
中,用戶也可以採用原型模式來創建新的bean實例
,從而實現每次獲取的是通過克隆生成的新實例,對其進行修改時對原有實例對象不造成任何影響。
五 總結
本文主要介紹了:什麼是原型模式、原型模式的優缺點以及使用場景。另外,簡單介紹了深拷貝和淺拷貝以及原型模式的實際應用案例。