導航
介紹原型模式的基本特點,對象拷貝的運用 。要了解 淺度拷貝 和 深度拷貝 的差別和使用。
原型設計模式介紹
- 定義:指原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象
- 特點:不需要知道任何建立細節,不調用構造函數
- 類型:建立型
- 适用場景:
- 類初始化消耗較多資源
- 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() ) );
}
}