引言
??回顧上一節(jié)我們講的狀態(tài)模式,這節(jié)我們來(lái)講一下原型模式。和原型模式相關(guān)的2個(gè)概念:淺拷貝和深拷貝。
示例地址
??Demo
先上類圖
再看定義
??用原型實(shí)例指定創(chuàng)建對(duì)象的種類饶火,并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
使用場(chǎng)景
??1. 類初始化需要消耗非常多的資源致扯,這個(gè)資源包括數(shù)據(jù)霸妹、硬件資源等尚镰,通過(guò)原型拷貝避免這些消耗醋闭。
??2. 通過(guò)new產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或者訪問權(quán)限镰绎,
??3. 一個(gè)對(duì)象需要提供給其他對(duì)象訪問,而且各個(gè)調(diào)用者可能都需要修改其值時(shí)耍群,可以考慮使用原型模式拷貝多個(gè)對(duì)象供調(diào)用者使用义桂,即保護(hù)性拷貝找筝。
注意事項(xiàng)
??通過(guò)實(shí)現(xiàn)Cloneable接口的原型模式在調(diào)用clone函數(shù)構(gòu)造實(shí)例時(shí)并不一定比通過(guò)new操作速度快,只有當(dāng)通過(guò)new構(gòu)造對(duì)象較為耗時(shí)或者說(shuō)成本較高時(shí)慷吊,通過(guò)clone方法才能夠獲得效率上的提升袖裕。因此,在使用Cloneable時(shí)需要考慮構(gòu)建對(duì)象的成本以及做一些效率上的測(cè)試溉瓶。
淺拷貝
??淺拷貝是將原始對(duì)象中的數(shù)據(jù)型字段拷貝到新對(duì)象中去急鳄,將引用型字段的“引用”復(fù)制到新對(duì)象中去,不把“引用的對(duì)象”復(fù)制進(jìn)去嚷闭,所以原始對(duì)象和新對(duì)象引用同一對(duì)象攒岛,新對(duì)象中的引用型字段發(fā)生變化會(huì)導(dǎo)致原始對(duì)象中的對(duì)應(yīng)字段也發(fā)生變化赖临。
1. 示例
/**
* 1.淺拷貝拷貝外層對(duì)象胞锰,對(duì)象里面的引用對(duì)象不進(jìn)行拷貝。
* 2.深拷貝需要進(jìn)行內(nèi)部的拷貝(人為進(jìn)行拷貝)兢榨。
* @author 512573717@qq.com
* @created 2018/7/13 下午4:10.
*/
public class Person implements Cloneable {
private String name;
private Son mSon;
public Person(String name, int age) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Son getSon() {
return mSon;
}
public void setSon(Son son) {
mSon = son;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", mSon=" + mSon +
'}';
}
}
/**
* @author 512573717@qq.com
* @created 2018/7/13 下午4:54.
*/
public class Son implements Cloneable {
public Son(String schoolName) {
this.schoolName = schoolName;
}
private String schoolName;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
protected Son clone() {
Son clone = null;
try {
clone = (Son) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // won't happen
}
return clone;
}
@Override
public String toString() {
return "Son{" +
"schoolName='" + schoolName + '\'' +
'}';
}
}
2. 調(diào)用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Person p = new Person("zhang");
Son son = new Son("實(shí)驗(yàn)二小");
p.setSon(son);
Person p1 = null;
try {
p1 = (Person) p.clone();
p1.setName("yang");
son.setSchoolName("希望小學(xué)");
System.out.println("----" + p.toString());
System.out.println("----" + p1.toString());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
3. 打印
System.out: ----Person{name='zhang', mSon=Son{schoolName='希望小學(xué)'}}
System.out: ----Person{name='yang', mSon=Son{schoolName='希望小學(xué)'}}
4. 分析原因
??Object 類提供的方法 clone 只是拷貝本對(duì)象嗅榕,
其對(duì)象內(nèi)部的數(shù)組、引用對(duì)象等都不拷貝吵聪,還是指向原生對(duì)象的內(nèi)部元素地址凌那。原始類型比如int,long,String(Java 就希望你把 String 認(rèn)為是基本類型,String 是沒有 clone 方法的)等都會(huì)被拷貝的吟逝。所以String改變了,引用的Son兩者還是一樣的帽蝶,沒有改變。
深拷貝
??深拷貝是在引用方面不同块攒,深拷貝就是創(chuàng)建一個(gè)新的和原始字段的內(nèi)容相同的字段励稳,是兩個(gè)一樣大的數(shù)據(jù)段,所以兩者的引用是不同的囱井,之后的新對(duì)象中的引用型字段發(fā)生改變驹尼,不會(huì)引起原始對(duì)象中的字段發(fā)生改變。
1. 示例
/**
* 深拷貝
*
* @author 512573717@qq.com
* @created 2018/7/13 下午4:53.
*/
public class PersonTwo implements Cloneable {
private String name;
private Son mSon;
public PersonTwo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Son getSon() {
return mSon;
}
public void setSon(Son son) {
mSon = son;
}
@Override
protected Object clone() {
PersonTwo clone = null;
try {
clone = (PersonTwo) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // won't happen
}
clone.mSon = mSon.clone();
return clone;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", mSon=" + mSon +
'}';
}
}
2. 調(diào)用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PersonTwo p = new PersonTwo("zhang");
Son son = new Son("實(shí)驗(yàn)二小");
p.setSon(son);
PersonTwo p1 = null;
p1 = (PersonTwo) p.clone();
p1.setName("yang");
son.setSchoolName("希望小學(xué)");
System.out.println("----" + p.toString());
System.out.println("----" + p1.toString());
}
3. 打印
System.out:----Person{name='zhang', mSon=Son{schoolName='希望小學(xué)'}}
System.out: ----Person{name='yang', mSon=Son{schoolName='實(shí)驗(yàn)二小'}}
4. 總結(jié)
??深拷貝到達(dá)了我們想要的效果庞呕,拷貝的時(shí)候所有的引用都變了
原型模式示例
??日常生活中新翎,我們總接收或者發(fā)送郵件,如果讓我們來(lái)寫一個(gè)程序發(fā)送郵件怎么寫住练。
1. 先來(lái)定義個(gè)發(fā)送郵件的類
/**
* 郵件模板Bean
*
* @author 512573717@qq.com
* @created 2018/7/13 下午3:02.
*/
public class Mail {
public String receiver;// 接收者
public String tail;// 結(jié)尾備注
private String context; // 內(nèi)容
private String sub; // 標(biāo)題
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public String getSub() {
return sub;
}
public void setSub(String sub) {
this.sub = sub;
}
}
2. 發(fā)送郵件
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* 發(fā)送郵件 */
final Mail mail = new Mail();
mail.setTail("xxx銀行的所有版權(quán)");
for (int i = 0; i < 100; i++) {
mail.setSub("i" + " 先生(女士) ");
mail.setReceiver("i0001122" + "@qq.com");
sendMail(mail);
}
}
public static void sendMail(Mail mail) {
System.out.println("標(biāo)題: " + mail.getSub() + "\t收件人"
+ mail.getReceiver() + "\t....發(fā)送成功地啰! ");
}
}
3. 存在問題
??如果使用單線程發(fā)送,按照一封郵件發(fā)出去需要 0.03秒, 1000封郵件需要30秒讲逛,可以接受亏吝。100萬(wàn)封郵件呢?大概就是幾小時(shí)妆绞,肯定行不通顺呕。那么我們換成多線程,將sendMail任務(wù)封裝枫攀,但是問題有來(lái)了,因?yàn)橛玫氖峭粋€(gè)對(duì)象株茶,如果線程1還沒發(fā)送完来涨,線程2執(zhí)行了,那么就造成了數(shù)據(jù)錯(cuò)亂启盛,線程1發(fā)送的名字變成了線程2的蹦掐。
4. 解決辦法
??使用mail.clone()將對(duì)象拷貝一份,產(chǎn)生一個(gè)新的對(duì)象僵闯,和原有對(duì)象一樣卧抗,然后再修改細(xì)節(jié)的數(shù)據(jù),如設(shè)置稱謂鳖粟,設(shè)置收件人地址等等社裆。而這種不通過(guò) new 關(guān)鍵字來(lái)產(chǎn)生一個(gè)對(duì)象,而是通過(guò)對(duì)象拷貝來(lái)實(shí)現(xiàn)的模式就叫做原型模式向图。
/**
* 郵件模板Bean
*
* @author 512573717@qq.com
* @created 2018/7/13 下午3:02.
*/
public class Mail implements Cloneable {
.......
.......
.......
// 進(jìn)行淺拷貝
@Override
protected Mail clone() throws CloneNotSupportedException {
Mail mail = (Mail) super.clone();
return mail;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < 100; i++) {
Mail cloneMail;
try {
cloneMail = mail.clone();
cloneMail.setSub("i" + " 先生(女士) ");
cloneMail.setReceiver("i0001122" + "@qq.com");
sendMail(cloneMail);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void sendMail(Mail mail) {
System.out.println("標(biāo)題: " + mail.getSub() + "\t收件人"
+ mail.getReceiver() + "\t....發(fā)送成功泳秀! ");
}
}
總結(jié)
- 對(duì)象拷貝時(shí),類的構(gòu)造函數(shù)是不會(huì)被執(zhí)行的榄攀。
??一個(gè)實(shí)現(xiàn)了 Cloneable 并重寫了 clone 方法的類 A,有一個(gè)構(gòu)造函數(shù)B嗜傅,通過(guò) new 關(guān)鍵字產(chǎn)生了一個(gè)對(duì)象 S,再然后通過(guò) S.clone()方式產(chǎn)生了一個(gè)新的對(duì)象 T檩赢,那么在對(duì)象拷貝時(shí)構(gòu)造函數(shù) B 是不會(huì)被執(zhí)行的吕嘀,Object 類的 clone 方法的原理是從內(nèi)存中(具體的說(shuō)就是堆內(nèi)存)以二進(jìn)制流的方式進(jìn)行拷貝,重新分配一個(gè)內(nèi)存塊贞瞒,那構(gòu)造函數(shù)沒有被執(zhí)行也是非常正常的了偶房。 - final 類型修飾的成員變量不能進(jìn)行深拷貝
- 在實(shí)際項(xiàng)目中,原型模式很少單獨(dú)出現(xiàn)憔狞,一般是和工廠方法模式一起出現(xiàn)蝴悉,通過(guò) clone的方法創(chuàng)建一個(gè)對(duì)象,然后由工廠方法提供給調(diào)用者瘾敢。原型模式先產(chǎn)生出一個(gè)包含大量共有信息的類拍冠,然后可以拷貝出副本,修正細(xì)節(jié)信息簇抵,建立了一個(gè)完整的個(gè)性對(duì)象庆杜。