定義
指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
不需要知道任何創建細節,不調用構造函數。
類型
創建型
適用場景
①. 類初始化消耗較多資源。
②. new產生的一個對象需要非常繁瑣的過程(數據準備、訪問權限等)。
③. 構造函數比較複雜。
④. 循環體中生產大量對象時。
優缺點
優點:
①. 原型模式性能比直接new一個對象性能高。
②. 簡化創建過程。
缺點:
①. 必須具備克隆方法。
②. 對克隆複雜對象或對克隆出的對象進行復雜改造時,容易引入風險。
③. 深拷貝、淺拷貝要運用得當。
代碼實現
以發送郵件並保留原始郵件爲例。
首先,創建一個郵件對象。
public class Mail implements Cloneable {
private String name;
private String emailAddress;
private String content;
public Mail() {
System.out.println("init Constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}' + "," + super.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("克隆對象");
return super.clone();
}
}
Mail這個對象要想使用克隆的方法,需要實現Cloneable接口,並重寫clone()方法。爲了直觀的看到輸出結果,修改了toString()方法。
需要發送郵件和存儲原始郵件,所以,寫一個操作郵件的工具類。
public class MailUtil {
/**
* 發送郵件
*/
public static void sendMail(Mail mail) {
String mailContent = "向姓名爲:{0},郵件地址爲:{1},郵件內容爲:{2}的同學發送郵件";
System.out.println(MessageFormat.format(mailContent, mail.getName(), mail.getEmailAddress(), mail.getContent()));
}
/**
* 存儲原始郵件
*/
public static void saveOriginMail(Mail mail) {
System.out.println("存儲原始origin記錄,originMail:" + mail.getContent());
}
}
最後編寫測試類。
public class MainTest{
public static void main(String[] args) throws CloneNotSupportedException {
Mail mail = new Mail();
mail.setContent("初始化模板");
System.out.println("初始化mail:" + mail + "\n");
for (int i = 0; i < 10; i++) {
//用原始的對象去克隆,使用克隆出來的對象去賦值
Mail tempMail = (Mail) mail.clone();
tempMail.setName("小明" + i);
tempMail.setEmailAddress(i + "@gmail.com");
tempMail.setContent("恭喜您,獲得了Java設計模式這門課程 ");
MailUtil.sendMail(tempMail);
System.out.println("克隆的mail:" + tempMail + "\n");
}
//存儲的時候,還是存儲原始的mail對象
MailUtil.saveOriginMail(mail);
}
}
通過控制檯查看結果
C:\android\java\jdk1.8\bin\java.exe...
init Constructor
初始化mail:Mail{name='null', emailAddress='null', content='初始化模板'},com.qfcwx.design.pattern.creational.prototype.Mail@29453f44
克隆對象
向姓名爲:小明0,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明0', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@5cad8086
克隆對象
向姓名爲:小明1,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明1', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@6e0be858
克隆對象
向姓名爲:小明2,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明2', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@61bbe9ba
克隆對象
向姓名爲:小明3,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明3', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@610455d6
克隆對象
向姓名爲:小明4,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明4', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@511d50c0
克隆對象
向姓名爲:小明5,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明5', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@60e53b93
克隆對象
向姓名爲:小明6,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明6', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@5e2de80c
克隆對象
向姓名爲:小明7,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明7', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@1d44bcfa
克隆對象
向姓名爲:小明8,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明8', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@266474c2
克隆對象
向姓名爲:小明9,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明9', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@6f94fa3e
存儲原始origin記錄,originMail:初始化模板
因爲截圖還要上傳至服務器,感覺很麻煩,所以直接將結果複製出來了。
通過上面的結果可知,雖然原始郵件通過new創建了對象。但是克隆對象都是通過原始對象的clone()方法生成的,並沒有使用構造器,每個對象的內存地址都不同。還保留了最原始的郵件。
擴展(深克隆、淺克隆)
創建一個實體類
public class Pig implements Cloneable {
private String name;
private Date birthday;
public Pig() {
}
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
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}' + "," + super.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
這個實體類,有姓名和生日兩個屬性。也實現了Cloneable接口,重寫clone()方法並重寫toString()方法。
編寫測試類。
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Pig pig = new Pig();
Date birthday = new Date(0L);
pig.setName("佩奇");
pig.setBirthday(birthday);
Pig newPig = (Pig) pig.clone();
System.out.println(pig);
System.out.println(newPig);
//修改時間
pig.getBirthday().setTime(666666666666L);
System.out.println(pig);
System.out.println(newPig);
}
}
這裏創建了一個Pig對象。然後使用clone()方法進行克隆。然後輸出兩個對象的結果。
底下對原始對象的生日屬性進行了修改。再一次輸出了兩個對象的結果。
預期結果:
原始對象(pig),兩次輸出結果不一樣。
克隆對象(newPig),兩次輸出結果一樣。
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970},com.qfcwx.design.pattern.creational.prototype.colne.Pig@610455d6
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970},com.qfcwx.design.pattern.creational.prototype.colne.Pig@511d50c0
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991},com.qfcwx.design.pattern.creational.prototype.colne.Pig@610455d6
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991},com.qfcwx.design.pattern.creational.prototype.colne.Pig@511d50c0
查看結果的時候,一下就驚呆了,這是什麼操作啊?明明只修改了原始對象(pig)的屬性值,爲什麼克隆對象的birthday值也會改變?
接着,博主進行debug發現,雖然兩個Pig對象不一樣,但是通過克隆出來的newPig對象和原始對象的birthday屬性是同一個Date對象。也就是說,當我們修改日期對象的時候,另一個對象中的這個日期對象也會發生變化。所以,這種克隆的方式就是淺克隆。
那麼如何進行深克隆,使得改變原始對象的值,不影響克隆對象的值?
其實也很簡單,具體操作就是,再重寫clone()方法的時候,對生日這個對象進行克隆。修改上面Pig實體類中的clone()方法。
@Override
protected Object clone() throws CloneNotSupportedException {
Pig pig = (Pig) super.clone();
//深克隆(單獨對生日對象進行克隆)
pig.birthday = (Date) pig.birthday.clone();
return pig;
//默認的克隆方式(淺克隆)
// return super.clone();
}
修改代碼如上,將原先的克隆邏輯注掉,改爲對引用對象(Date)也進行克隆。然後重新運行程序,查看結果。就達到了當初預期的效果。
所以,如果對象屬性中有引用數據類型,那麼在進行克隆的時候,也是要對屬性進行克隆的,這樣才能避免不必要的BUG。
相關源碼
- ArrayList,HashMap中都重寫了clone方法
- Mybatis中的CacheKey
結語
1024程序員節是廣大程序員的共同節日。1024是2的十次方,二進制計數的基本計量單位之一。針對程序員經常週末加班與工作日熬夜的情況,部分互聯網機構倡議每年的10月24日爲1024程序員節,在這一天建議程序員拒絕加班。
程序員就像是一個個1024,以最低調、踏實、核心的功能模塊搭建起這個科技世界。1G=1024M,而1G與1級諧音,也有一級棒的意思。
今天是程序員節,也祝各位讀者程序員節快樂。希望以後,我們只漲薪水,不改需求。