寫在前面:對於原型模式的介紹,網上有很多。但是給我的感覺,有些介紹並不是在單純的介紹原型模式本身的意義,而是附加了其他的設計模式。至少我在看過很多文章之後,不僅未清晰的瞭解原型模式的本質,反而愈發感覺糊里糊塗。當然,這或許是我個人還沒達到理解這種模式的水平。
本文關於原型模式的介紹,基本是出於我個人依據定義的理解。或許是正確的,或許存在謬誤,歡迎大家留言指正討論。
目錄
首先看下原型模式的定義:
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
Prototype原型模式是一種創建型設計模式,Prototype模式允許一個對象再創建另外一個可定製的對象,根本無需知道任何如何創建的細節,工作原理是:通過將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝它們自己來實施創建。
一、模式定義分析
通過上面的定義,得到的關鍵詞是:對象複製。那我們就從對象和複製展開來討論。
1、實現對象複製之前先回顧一下以下知識點:
- 在Java中,對象實例的創建一般都要通過new關鍵字,需要通過構造函數來實例化。既然要複製,可能要避開這個限制。
- 在Java中,創建的對象除去基本數據類型的非包裝類、字符串對象(在JDK7之前,待考證)等,絕大多數的對象都是在堆內存中創建的。複製,就需要在內存空間中創建出一個獨立的對象出來。
- 在Java中,提供了語言級別的複製支持,那就是Cloneable接口。這個接口是個標記接口,裏面並沒有要實現的方法。因爲所有的對象都隱式的繼承自Object類,Object類中有一個clone方法。這個clone方法有2個限制:①方法修飾符是protected,無法在非繼承類內部使用,所以要在重寫時把修飾符改爲public。②如果沒有實現Cloneable接口而直接調用clone方法,會報錯。
- 對象中既有基本數據類型的成員變量,也有可能有引用類型的成員變量,拷貝方式肯定有區別。所以需要了解淺拷貝和深拷貝的概念。
2、哪些場景下應該考慮使用複製的模式呢?
因爲和複製做對比的,主要是通過new關鍵字來創建對象,所以來看下new可能會遇到的性能問題。
①當new對象需要大量繁瑣的準備工作時,複製會提高性能。
示例場景
public Demo getDemoInstance() {
Object obj = prepareObject();// 創建對象前需要執行一些耗時操作
Demo demo = new Demo(obj);
}
public Demo(Object obj) {
// 解析配置、初始化環境、設置變量等等操作
}
②當需要多次重複的調用new關鍵字執行構造函數時,累積的時間損耗也是可以考慮的優化點。
for(int i=0; i<1000000; i++) {
Demo demo = new Demo(); // 每次都要新建對象,而new又比較耗時的時候
handle(demo);
}
③當一個對象創建耗時,單實例又存在併發安全問題時,也可以考慮複製一份。
寫到這裏忍不住想起ThreadLocal類,這個號稱爲每個線程拷貝一份副本的類是怎麼實現的?深拷貝還是淺拷貝呢?拷貝時如果泛型裏的類沒有實現Cloneable接口會怎樣呢?回頭再去看看...
3、複製在哪裏操作?需要注意什麼?
- 儘量減少new對象的地方,如果系統中大量通過new創建的對象,那麼複製也就沒啥意義了
- 複製需要在用到原型類對象的地方來操作
二、代碼實現:
1、原型類代碼
public class Prototype implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
2、調用類代碼
public class PrototypeClient {
public static void main(String[] args) throws CloneNotSupportedException {
Prototype raw = new Prototype();
for (int i=0; i<1000; i++) {
Prototype clone = (Prototype) raw.clone();
System.out.println(clone);
}
}
}
三、補充說明:
綜上,複製之前我們需要先有一個現成的對象,但是這個現成的對象數量不宜過多,否則就失去了複製的意義。原始對象可以考慮使用緩存的方式、單例模式等。