1、前言
本篇介紹 Java 設計模式中建立型模式的最後一種–原型模式。上篇設計模式主題爲 《Java 設計模式之建造者模式(四)》java
2、簡單介紹
原型模式是一種對象建立型模式,它採起復制原型對象的方法來建立對象的實例。使用原型模式建立的實例,具備與原型同樣的數據。設計模式
2.1 特色
1) 由原型對象自身建立目標對象。即對象建立這一動做發自原型對象自己。ide
2) 目標對象是原型對象的一個克隆。即經過原型模式建立的對象,不單單與原型對象具備相同的結構,還與原型對象具備相同的值。函數
3) 根據對象克隆深度層次的不一樣,有淺度克隆與深度克隆。性能
2.2 應用場景
1) 在建立對象的時候,咱們不僅是但願被建立的對象繼承其父類的基本結構,還但願繼承原型對象的數據。this
2) 但願對目標對象的修改不影響既有的原型對象(深度克隆的時候能夠徹底互不影響)。spa
3) 隱藏克隆操做的細節。不少時候,對對象自己的克隆須要涉及到類自己的數據細節。.net
3、實現方式
咱們以克隆羊爲例,實現類須要實現 Cloneable 接口,同時要重寫從 Object 類中繼承的 clone 方法:設計
public class Sheep implements Cloneable{ private String name; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(1365215478956L); Sheep sheep = new Sheep(); sheep.setName("多利"); sheep.setBirthday(date); Sheep clone = (Sheep) sheep.clone(); System.out.println("克隆羊名字:" + clone.getName()); System.out.println("克隆羊生日:" + clone.getBirthday()); } }
客戶端:code
結果:
克隆羊名字:多利 克隆羊生日:Sat Apr 06 10:31:18 CST 2013
不過,上邊的克隆方式屬於淺度克隆,當對象中包含引用類型的屬性時,這樣淺克隆方式會存在一個問題。咱們試着修改 date 的值:當咱們須要更多的羊時,只需調用第一隻羊的 clone 方法便可,省去了給新建的對象設置屬性的操做。
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(1365215478956L); Sheep sheep = new Sheep(); sheep.setName("多利"); sheep.setBirthday(date); Sheep clone = (Sheep) sheep.clone(); System.out.println("克隆羊名字:" + clone.getName()); System.out.println("克隆羊生日:" + clone.getBirthday()); System.out.println("--------------------------------"); // 修改本體羊生日 date.setTime(3246584261356L); System.out.println("本體羊生日:" + sheep.getBirthday()); System.out.println("克隆羊生日:" + clone.getBirthday()); } }
克隆羊名字:多利 克隆羊生日:Sat Apr 06 10:31:18 CST 2013 -------------------------------- 本體羊生日:Thu Nov 17 12:57:41 CST 2072 克隆羊生日:Thu Nov 17 12:57:41 CST 2072
結果打印:
咱們修改本體羊的日期,結果克隆羊的日期也發生變化。
由於淺度克隆只對基本數據類型的值進行備份,而引用類型的數據只是拷貝了指向對象的引用而已。其內存表現形式以下圖:
從圖中可知,兩隻羊共用一個日期對象。所以,當咱們修改日期時,兩隻羊的日期也發生變化。
爲了解決這一個問題,咱們可使用深度克隆方式,有 2 種方式能夠實現深度克隆。
方式一:在 clone 方法中將全部引用類型數據進行克隆。
實體類:
@Override protected Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Sheep sheep = (Sheep) obj; // 克隆時間 sheep.birthday = (Date) this.birthday.clone(); return sheep; }
在運行客戶端代碼時,結果以下:在實體類的 clone 方法中,將引用類型的數據一塊兒克隆。
克隆羊名字:多利 克隆羊生日:Sat Apr 06 10:31:18 CST 2013 -------------------------------- 本體羊生日:Thu Nov 17 12:57:41 CST 2072 克隆羊生日:Sat Apr 06 10:31:18 CST 2013
由於全部序列化方式,所以實體必須實現 Serializable 接口。方式二:使用序列化和反序列化進行克隆
實體類:
public class Sheep implements Cloneable,Serializable{ ... }
public class Client { public static void main(String[] args) throws CloneNotSupportedException,ClassNotFoundException, IOException { Date date = new Date(1365215478956L); Sheep sheep = new Sheep(); sheep.setName("多利"); sheep.setBirthday(date); //經過序列化和反序列化進行克隆 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(sheep); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); Sheep clone = (Sheep) ois.readObject(); System.out.println("克隆羊名字:" + clone.getName()); System.out.println("克隆羊生日:" + clone.getBirthday()); System.out.println("--------------------------------"); // 修改本體羊生日 date.setTime(3246584261356L); System.out.println("本體羊生日:" + sheep.getBirthday()); System.out.println("克隆羊生日:" + clone.getBirthday()); } }
客戶端:
打印結果與方式一的結果一致。
深度克隆的內存表現形式以下圖:
4、性能比較
使用原型模式建立對象並不會調用類的構造方法。所以,在構造函數調用長且須要重複建立該類的實例的狀況下,使用原型模式建立對象的優點就體現出來了。
實體類:
public class Sheep implements Cloneable{ private String name; private Date birthday; public Sheep() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
客戶端:延遲構造器調用的時間。
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Sheep sheep = new Sheep(); int num = 1000; long t1 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { Sheep tmp = new Sheep(); } System.out.println("new 方式耗時:" + (System.currentTimeMillis() - t1) + "ms"); long t2 = System.currentTimeMillis(); for (int i = 0; i < num; i++) { Sheep tmp = (Sheep) sheep.clone(); } System.out.println("克隆方式耗時:" + (System.currentTimeMillis() - t2) + "ms"); } }
new 方式耗時:10298ms 克隆方式耗時:1ms
結果: