設計模式(5)—— 建立型 —— 原型(Prototype)
阿新 • • 發佈:2018-11-19
導航
介紹原型模式的基本特點,物件拷貝的運用 。要理解 淺度拷貝 和 深度拷貝 的區別和使用。
原型設計模式介紹
- 定義:指原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件
- 特點:不需要知道任何建立細節,不呼叫建構函式
- 型別:建立型
- 適用場景:
- 類初始化消耗較多資源
- new產生的一個物件需要非常頻繁的過程,例如資料準備,訪問許可權等。
- 建構函式較為複雜
- 迴圈體中生產大量物件時
- 優點
- 效能比直接new一個物件效能高
- 簡化建立過程
- 缺點
- 必須配備克隆方法,克隆方法是這個模式的核心。(Java提供cloneable介面表示物件是可拷貝的;必須重寫Object的clone方法)
- 對克隆複雜物件或克隆出物件進行復雜改造時,容易引入風險。
- 深拷貝,淺拷貝要運用得恰當。
一句話來說,就是實現類的克隆,其中包含深度拷貝,淺度拷貝。
程式碼實現
實現程式碼的業務場景:及時通訊app,某個人在特定時間內傳送很多訊息給好友。
首先,定義簡單訊息類,以及傳送訊息的工具類。
/**
* 定義訊息類。
* 注意點:implement Cloneable ; @Override clone
*/
public class Message implements Cloneable {
private String content;
private String senderName;
private String receiverName;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSenderName () {
return senderName;
}
public void setSenderName(String senderName) {
this.senderName = senderName;
}
public String getReceiverName() {
return receiverName;
}
public void setReceiverName(String receiverName) {
this.receiverName = receiverName;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 定義傳送訊息類,簡單的print一下。以代表相關的傳送邏輯
*/
public class MessageUtil {
public static void sendMessage(Message msg){
System.out.println( msg.getSenderName() +
" is Sending a message : {" +
msg.getContent() +
"} to " +
msg.getReceiverName() );
}
}
下面是測試類:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Message msg = new Message();
msg.setSenderName("Me");
msg.setContent("預設內容");
msg.setReceiverName("預設接收者");
// 模擬業務場景,此時一個人在某個時間點需要傳送多條訊息。
for( int i= 0 ; i < 5 ; i ++ ){
Message clonedMsg = (Message) msg.clone();
clonedMsg.setContent("第" + (i+1) + "條訊息");
clonedMsg.setReceiverName("第" + (i+1) + "個人");
MessageUtil.sendMessage(clonedMsg);
}
}
}
從測試程式碼容易看出,業務頻繁要建立物件例項。並且這個例項我們已知。那麼我們就利用現成的已知物件來克隆新的物件。
讓克隆抽象化
// 抽象可克隆的類。
public class AbstractCloneableClass implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 具體的實體類
public class Entity extends AbstractCloneableClass {
private String content;
private String senderName;
private String receiverName;
/**
* 下面是getter和setter,我們省略。僅僅為了展示克隆抽象化的體現
*/
//由於繼承抽象類的關係,我們在實體類中不再需要重寫clone和實現cloneable介面了
}
程式碼通過註釋很容易理解。
淺度拷貝出現的問題
前面我們的成員變數都為String。其實對於成員變數只有基本資料型別和String型別的類,我們在重寫clone函式時,直接使用super.clone()就足以解決問題。但是當我們的成員變數是一個類的時候,此時的拷貝只能拷貝我們前面所說的幾種常見的資料型別。這就要求我們在重寫clone函式時,進行深度拷貝。具體的程式碼實現如下:
現在對於一個User類,我們有它的基本資訊name,birthday。
按照前面的實現思路,實現下面的拷貝:
public class User implements Cloneable {
//User類,注意到成員變數birthday的型別為Birthday自定義Class型別。
private String name;
private BirthDay birthday;
public User(String name, BirthDay time){
this.name = name;
this.birthday = time;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BirthDay getBirthday() {
return birthday;
}
public void setBirthday(BirthDay birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* 生日類。這個類在User類中作為成員變數
*/
public class BirthDay {
private String year;
private String month;
private String day;
public BirthDay(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
this.month = month;
}
public String getDay() {
return day;
}
public void setDay(String day) {
this.day = day;
}
}
下面是測試程式碼用例:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User("XiaoMing", new BirthDay( "1998", "1", "1" ) );
User clonedUser = (User) user.clone();
/**測試兩個整體User例項,看是否是指向同一地址。執行結果:
user [email protected]
cloned user [email protected]
兩者是否指向同一記憶體地址?false
*/
System.out.println( "user [email protected]" + user.hashCode() +
"\ncloned user [email protected]" + clonedUser.hashCode() +
"\n兩者是否指向同一記憶體地址?" + (user==clonedUser) );
/**
* 下面是對兩個物件內部的birthday物件進行測試。下面是輸出結果
User : XiaoMing @1878246837
Cloned User : XiaoMing @1878246837
生日是否指向同一記憶體地址:true
*/
System.out.println( "\nUser : " + user.getName() +
" @" + user.getBirthday().hashCode() );
System.out.println( "Cloned User : " + clonedUser.getName() +
" @" + clonedUser.getBirthday().hashCode() );
System.out.println( "生日是否指向同一記憶體地址:" + ( user.getBirthday() == clonedUser.getBirthday() ) );
}
}
從結果我們可以看到,兩個User物件內部的birthday物件的hashCode相等,並且指向同一記憶體地址。這樣的拷貝並不完全。
深度拷貝解決方案
下面是深拷貝的方法:
@Override
protected Object clone() throws CloneNotSupportedException {
User clonedUser = (User) super.clone();
clonedUser.birthday = new BirthDay( clonedUser.getBirthday().getYear(),
clonedUser.getBirthday().getMonth(),
clonedUser.getBirthday().getDay() );
return clonedUser;
}
測試用例:
// 用例生成的hashCode的具體值不必在乎。
// 在乎的是比較hashCode的相等性。
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User("XiaoMing", new BirthDay( "1998", "1", "1" ) );
User clonedUser = (User) user.clone();
/**測試兩個整體User例項,看是否是指向同一地址。執行結果:
user [email protected]
cloned user [email protected]
兩者是否指向同一記憶體地址?false
*/
System.out.println( "user [email protected]" + user.hashCode() +
"\ncloned user [email protected]" + clonedUser.hashCode() +
"\n兩者是否指向同一記憶體地址?" + (user==clonedUser) );
/**
* 下面是對兩個物件內部的birthday物件進行測試。下面是輸出結果
User : XiaoMing @1878246837
Cloned User : XiaoMing @929338653
生日是否指向同一記憶體地址:false
*/
System.out.println( "\nUser : " + user.getName() +
" @" + user.getBirthday().hashCode() );
System.out.println( "Cloned User : " + clonedUser.getName() +
" @" + clonedUser.getBirthday().hashCode() );
System.out.println( "生日是否指向同一記憶體地址:" + ( user.getBirthday() == clonedUser.getBirthday() ) );
}
}