目錄
-
/ 前言 /
原型模式的使用非常簡單,代碼量非常少,相比與其它的設計模式都有獨立的類來承載設計,原型模式則是有語言級別的支持作爲後盾,但是如果我們想深入的瞭解其內部原理的話就需要去看JVM的源碼
/** * Object類中clone()方法的源碼,native關鍵字表示它是一個本地方法 * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. The general * intent is that, for any object {@code x}, the expression: * @see java.lang.Cloneable */ protected native Object clone() throws CloneNotSupportedException;
關於原型模式其實能說的並不是很多,使用也很簡單,唯一有點意義的可能只有淺拷貝與深拷貝了,相信這倆個名詞大家不是第一次看到了,本文也會詳細講解什麼是淺拷貝與深拷貝
-
/ 1 / 爲什麼要使用原型模式
我們來看倆個場景
-
在實際開發中總是會碰到一些複雜的對象,例如內部屬性可能很複雜,但是我們需要重複的創建該對象,爲了方便我們可以直接使用原型模式克隆一個一模一樣的對象出來
-
又或者是我們在AOP中動態的取到了一個對象並要使用該對象完成一些業務邏輯,但是我們根本不知道對象的類型是什麼,爲了不影響到原有對象的狀態,所有我們最好的方式就是使用原型模式直接複製一個一模一樣的出來
注意 :要做到上述2中要求的爲了不影響原有對象的狀態我們必須使用深拷貝來實現,並且你獲取對象的類型必須支持拷貝即實現了
Cloneable
接口並覆寫了clone()
方法
以上的場景最好的處理方式就是使用原型模式來解決
-
-
/ 2 / 原型模式怎麼用
我們先看一下原型模式的定義:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
定義其實說的很明白了,被克隆的對象是用來指定創建類型的(告訴JVM要創建的對象的類型),通過拷貝的方式直接創建新的對象
我在學習原型模式伊始,當時有倆個疑問
1. JVM直接創建對象那麼會不會只是將引用地址的值複製了一份,只是棧空間了面多出了一個變量
2. JVM直接創建對象會不會像
new
一個對象一樣調用構造器來創建呢我們直接來看代碼並解釋這倆個疑問
1.是否只是複製了引用地址值
@Setter @Getter public class ArchetypalPattern implements Cloneable{ private Integer age; private String name; private Integer sex; public ArchetypalPattern () { this.age = 18; this.name = "wise"; this.sex = 1; } @Override public ArchetypalPattern clone () { ArchetypalPattern archetypalPattern = null; try { archetypalPattern = (ArchetypalPattern)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return archetypalPattern; } } //客戶端 public static void main(String[] args) { ArchetypalPattern demo = new ArchetypalPattern(); ArchetypalPattern clone = demo.clone(); System.out.println(demo); System.out.println(clone); }
輸出
com.example.demo.ArchetypalPattern@2f4d3709
com.example.demo.ArchetypalPattern@4e50df2e
通過打印結果我們可以得到一個結論,執行
clone()
方法後,JVM會創建一個新的對象而不是將原本對象的引用地址進行傳遞,而且因爲是JVM直接創建的,所以性能會比new
要好2.是否會調用構造器
@Setter @Getter @ToString public class ArchetypalPattern implements Cloneable{ private Integer age; private String name; private Integer sex; public ArchetypalPattern () { this.age = 18; this.name = "wise"; this.sex = 1; } @Override public ArchetypalPattern clone () { ArchetypalPattern archetypalPattern = null; try { archetypalPattern = (ArchetypalPattern)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return archetypalPattern; } public static void main(String[] args) { ArchetypalPattern demo = new ArchetypalPattern(); demo.setAge(20); ArchetypalPattern clone = demo.clone(); System.out.println(demo); System.out.println(clone); } }
輸出
ArchetypalPattern(age=20, name=wise, sex=1)
ArchetypalPattern(age=20, name=wise, sex=1)
我們在客戶端創建
demo
對象後對age
屬性進行了重新賦值,而clone()
方法返回的age
屬性也是重新賦值後的,所以我們可以得出結論,clone()
方法不會去執行構造器總結一下原型模式:
1、JVM在克隆時是直接創建了一個全新的對象
2、JVM在克隆時創建對象並不會調用構造方法
以上就是原型模式簡單的應用,接下來我們來聊一下淺拷貝與深拷貝
-
/ 3 / 淺拷貝與深拷貝
我們知道Java的數據類型分爲基本數據類型(int,float)和引用數據類型(Object,Array),淺拷貝和深拷貝只能應用到引用數據類型上面,也只有引用數據類型存在地址和空間的概念
我們先來看一下淺拷貝的具體表現
@Setter @Getter class InnerProperty { private Integer a; } @Setter @Getter public class ArchetypalPattern implements Cloneable{ private Integer age; private String name; private Integer sex; private InnerProperty property; public ArchetypalPattern () { property = new InnerProperty(); this.age = 18; this.name = "wise"; this.sex = 1; } @Override public ArchetypalPattern clone () { ArchetypalPattern archetypalPattern = null; try { archetypalPattern = (ArchetypalPattern)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return archetypalPattern; } } public static void main(String[] args) { ArchetypalPattern demo = new ArchetypalPattern(); ArchetypalPattern clone = demo.clone(); System.out.println(demo); System.out.println(clone); System.out.println(demo.getProperty()); System.out.println(clone.getProperty()); }
輸出
com.example.demo.ArchetypalPattern@2f4d3709
com.example.demo.ArchetypalPattern@4e50df2e
com.example.demo.InnerProperty@1d81eb93
com.example.demo.InnerProperty@1d81eb93
打印結果的前倆行沒有什麼問題,符合我們之前對原型模式的總結,但是最後倆行的數據卻變成了一模一樣的,也就是說JVM在克隆時只是新創建指定類型(ArchetypalPattern)的對象,並沒有對該類型中的引用類型數據(InnerProperty)進行新建,所以導致了現在的結果,原因其實也很簡單,InnerProperty類沒有去實現Cloneable接口,引用類型的數據涉及到了內存的分配,棧空間內變量存儲的也只是內存的地址值而已,由此可以判斷出,如果你不手動對指定類型實例中的數據進行操作,那麼JVM在克隆時會直接將原有實例中的數據都複製一份到新的對象中
那麼深拷貝的結論也就呼之欲出了,我們直接上代碼
@Setter @Getter class InnerProperty implements Cloneable{ private Integer a; @Override public InnerProperty clone () { InnerProperty innerProperty = null; try { innerProperty = (InnerProperty)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return innerProperty; } } @Setter @Getter public class ArchetypalPattern implements Cloneable{ private Integer age; private String name; private Integer sex; private InnerProperty property; public ArchetypalPattern () { property = new InnerProperty(); this.age = 18; this.name = "wise"; this.sex = 1; } @Override public ArchetypalPattern clone () { ArchetypalPattern archetypalPattern = null; try { archetypalPattern = (ArchetypalPattern)super.clone(); archetypalPattern.property = this.property.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return archetypalPattern; } } public static void main(String[] args) { ArchetypalPattern demo = new ArchetypalPattern(); ArchetypalPattern clone = demo.clone(); System.out.println(demo); System.out.println(clone); System.out.println(demo.getProperty()); System.out.println(clone.getProperty()); }
輸出
com.example.demo.ArchetypalPattern@2f4d3709
com.example.demo.ArchetypalPattern@4e50df2e
com.example.demo.InnerProperty@1d81eb93
com.example.demo.InnerProperty@7291c18f
我們在拷貝
ArchetypalPattern
類型對象時同時拷貝了其內部引用類型InnerProperty
,這樣就可以實現深層次的拷貝,也就是說如果指定類型中存在引用類型的數據,如果想對其也進行拷貝,那麼則需要在其上一層完成,這裏的拷貝是指原型模式級別的拷貝(創建新的對象),而不只是簡單的複製黏貼我們來總結一下
淺拷貝 :只拷貝指定類型,如果指定類型的屬性中有引用類型的數據只會拷貝其地址值
深拷貝 :需要被拷貝的所有類型必須都實現
Cloneable
接口並覆寫clone()
方法,拷貝指定類型中的引用類型數據需要在指定類型層面進行處理 -
/ 4 / 結語
原型模式結束後我們的創建型設計模式就全部分享完畢了,接下來我們會進入到行爲型設計模式的分享,敬請期待
原型模式到此結束~