4. 設計模式–原型模式
- 2022 年 10 月 11 日
- 筆記
原型模式
在java中我們知道通過new關鍵字創建的對象是非常繁瑣的(類加載判斷,內存分配,初始化等),在我們需要大量對象的情況下,原型模式就是我們可以考慮實現的方式。
原型模式我們也稱為克隆模式,即一個某個對象為原型克隆出來一個一模一樣的對象,該對象的屬性和原型對象一模一樣。而且對於原型對象沒有任何影響。原型模式的克隆方式有兩種:淺克隆和深度克隆
原型模式 | 說明 |
---|---|
淺克隆 | 只是拷貝本對象,其對象內部的數組、引用對象等都不拷貝,還是指向原生對象的內部元素地址 |
深度克隆 | 深複製把要複製的對象所引用的對象都複製了一遍 |
淺克隆
被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不複製它所引用的對象。 Object類提供的方法clone只是拷貝本對象 , 其對象內部的數組、引用對象等都不拷貝,還是指向原生對象的內部元素地址
實現
被克隆的對象必須Cloneable,Serializable這兩個接口
原型類
package com.dpb.prototype;
import java.io.Serializable;
import java.util.Date;
/**
* 原型類:被克隆的類型
* @author dengp
*
*/
public class User implements Cloneable,Serializable{
private String name;
private Date birth;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 實現克隆的方法
*/
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
測試類
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date(1231231231231l);
User user = new User();
user.setName("波波烤鴨");
user.setAge(18);
user.setBirth(date);
System.out.println("----輸出原型對象的屬性------");
System.out.println(user);
System.out.println(user.getName());
System.out.println(user.getBirth());
// 克隆對象
User user1 =(User) user.clone();
// 修改原型對象中的屬性
date.setTime(123231231231l);
System.out.println(user.getBirth());
// 修改參數
user1.setName("dpb");
System.out.println("-------克隆對象的屬性-----");
System.out.println(user1);
System.out.println(user1.getName());
System.out.println(user1.getBirth());
}
淺克隆的問題:雖然產生了兩個完全不同的對象,但是被複制的對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用都仍然指向原來的對象。
深度克隆
被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。
實現的效果是:
深度克隆(deep clone)有兩種實現方式,第一種是在淺克隆的基礎上實現,第二種是通過序列化和反序列化實現,我們分別來介紹
第一種方式
在淺克隆的基礎上實現
原型類:
package com.dpb.prototype;
import java.io.Serializable;
import java.util.Date;
/**
* 原型類:被克隆的類型
* 深度克隆測試
* @author dengp
*
*/
public class User2 implements Cloneable,Serializable{
private String name;
private Date birth;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 實現克隆的方法
* 深度克隆(deep clone)
*/
public Object clone() throws CloneNotSupportedException{
Object object = super.clone();
// 實現深度克隆(deep clone)
User2 user = (User2)object;
user.birth = (Date) this.birth.clone();
return object;
}
}
測試代碼
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date(1231231231231l);
User2 user = new User2();
user.setName("波波烤鴨");
user.setAge(18);
user.setBirth(date);
System.out.println("----輸出原型對象的屬性------");
System.out.println(user);
System.out.println(user.getName());
System.out.println(user.getBirth());
// 克隆對象
User2 user1 =(User2) user.clone();
// 修改原型對象中的屬性
date.setTime(123231231231l);
System.out.println(user.getBirth());
// 修改參數
user1.setName("dpb");
System.out.println("-------克隆對象的屬性-----");
System.out.println(user1);
System.out.println(user1.getName());
System.out.println(user1.getBirth());
}
我們發現克隆的對象的屬性並沒有隨着我們對Date的修改而改變,說明克隆對象的Date屬性和原型對象的Date屬性引用的不是同一個對象,實現的深度複製。
第二種方式:序列化和反序列化
名稱 | 說明 |
---|---|
序列化 | 把對象轉換為位元組序列的過程。 |
反序列化 | 把位元組序列恢復為對象的過程。 |
public static void main(String[] args) throws CloneNotSupportedException, Exception {
Date date = new Date(1231231231231l);
User user = new User();
user.setName("波波烤鴨");
user.setAge(18);
user.setBirth(date);
System.out.println("-----原型對象的屬性------");
System.out.println(user);
System.out.println(user.getName());
System.out.println(user.getBirth());
//使用序列化和反序列化實現深複製
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
//克隆好的對象!
User user1 = (User) ois.readObject();
// 修改原型對象的值
date.setTime(221321321321321l);
System.out.println(user.getBirth());
System.out.println("------克隆對象的屬性-------");
System.out.println(user1);
System.out.println(user1.getName());
System.out.println(user1.getBirth());
}
實現了和第一種實現方式相同的效果~實現了深度克隆
原型模式和直接new對象方式的比較
當我們需要大量的同一類型對象的時候可以使用原型模式,下面是兩種方式的性能對比:
用兩種方式同時生成10個對象
/**
* 測試普通new方式創建對象和clone方式創建對象的效率差異!
* 如果需要短時間創建大量對象,並且new的過程比較耗時。則可以考慮使用原型模式!
* @author 波波烤鴨
*
*/
public class Client4 {
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
User t = new User();
}
long end = System.currentTimeMillis();
System.out.println("new的方式創建耗時:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
User t = new User();
for(int i=0;i<size;i++){
User temp = (User) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式創建耗時:"+(end-start));
}
public static void main(String[] args) throws Exception {
testNew(10);
testClone(10);
}
}
class User implements Cloneable { //用戶
public User() {
try {
Thread.sleep(10); //模擬創建對象耗時的過程!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //直接調用object對象的clone()方法!
return obj;
}
}
輸出結果:
new的方式創建耗時:108
clone的方式創建耗時:11
用兩種方式同時生成1000個對象
/**
* 測試普通new方式創建對象和clone方式創建對象的效率差異!
* 如果需要短時間創建大量對象,並且new的過程比較耗時。則可以考慮使用原型模式!
* @author 波波烤鴨
*
*/
public class Client4 {
public static void testNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
User t = new User();
}
long end = System.currentTimeMillis();
System.out.println("new的方式創建耗時:"+(end-start));
}
public static void testClone(int size) throws CloneNotSupportedException{
long start = System.currentTimeMillis();
User t = new User();
for(int i=0;i<size;i++){
User temp = (User) t.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone的方式創建耗時:"+(end-start));
}
public static void main(String[] args) throws Exception {
testNew(1000);
testClone(1000);
}
}
class User implements Cloneable { //用戶
public User() {
try {
Thread.sleep(10); //模擬創建對象耗時的過程!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //直接調用object對象的clone()方法!
return obj;
}
}
輸出結果:
new的方式創建耗時:10836
clone的方式創建耗時:10
小結:通過clone的方式在獲取大量對象的時候性能開銷基本沒有什麼影響,而new的方式隨着實例的對象越來越多,性能會急劇下降,所以原型模式是一種比較重要的獲取實例的方式。大家應該掌握好。
開發中的應用場景
原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的方法創建一個對象,然後由工廠方法提供給調用者。
• spring中bean的創建實際就是兩種:單例模式和原型模式。(原型模式需要和工廠模式搭配起來)