
前情提要
上集講到, 小光請(qǐng)來堂哥大龍作為自己的代理與飲品供應(yīng)商談判, 最終大龍用自己豐富的商場(chǎng)經(jīng)驗(yàn)幫小光拿到合適的價(jià)格.
小光也是嘗到了代理的甜頭, 開始將店里的更多工作交給表妹來打理, 自己騰出功夫去選新的分店地址了.
所有示例源碼已經(jīng)上傳到Github, 戳這里
新店建設(shè)
根據(jù)光谷店的經(jīng)營(yíng)經(jīng)驗(yàn), 很快, 小光就選好了分店的地址---創(chuàng)業(yè)街. 還是為了造福廣大屌絲單身程序猿們啊, 哈哈.
分店的建設(shè)相對(duì)第一家店的開辟來說也是簡(jiǎn)單了很多, 在光谷店的探索, 諸如熱干面生產(chǎn)流程, 飲料機(jī)機(jī)制, 活動(dòng)策略等都可以復(fù)制過來用. 簡(jiǎn)單來說, 就是復(fù)制成功原型, 如下:
照例, 抽象出一個(gè)公司的類:
public class Company implements Cloneable {
// 此處我們假裝省略了N多, 諸如活動(dòng)策略, 飲料機(jī), 熱干面生產(chǎn)流程等.
// 再此僅以飲品為例
private ArrayList<String> drinks = new ArrayList<>();
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addDrink(String drink) {
drinks.add(drink);
}
@Override
protected Company clone() {
Company company = null;
try {
company = (Company) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
@Override
public String toString() {
return "{" +
"名字: '" + getName() + '\'' +
", 飲品: " + drinks + '\'' +
'}';
}
}
光谷店:
public class OpticalValleyCompany extends Company {
public OpticalValleyCompany() {
setName("光谷軟件園分店");
addDrink("橙汁");
addDrink("可樂");
addDrink("酸梅湯");
}
}
看下小光是如何復(fù)制光谷店的成功, 創(chuàng)建新的創(chuàng)業(yè)街分店的:
public class XiaoGuang {
public static void main(String[] args) {
// new 光谷店
Company ovCompany = new OpticalValleyCompany();
System.out.println("光谷店: " + ovCompany);
// 在光谷店的基礎(chǔ)上clone SBI店
Company sbiCompany = ovCompany.clone();
sbiCompany.setName("創(chuàng)業(yè)街分店");
System.out.println("SBI店: " + sbiCompany);
}
}
output:
光谷店: {名字: '光谷軟件園分店', 飲品: [橙汁, 可樂, 酸梅湯]'}
SBI店: {名字: '創(chuàng)業(yè)街分店', 飲品: [橙汁, 可樂, 酸梅湯]'}
看樣子很成功, 小光開始準(zhǔn)備試運(yùn)營(yíng)了.
試運(yùn)營(yíng)
小光信心滿滿的開始了新店的試運(yùn)營(yíng). 為了慶祝分店開張, 小光新拿了一款飲料XDrink在新店做活動(dòng), 買熱干面贈(zèng)送飲料.
// 在光谷店的基礎(chǔ)上clone SBI店
Company sbiCompany = ovCompany.clone();
sbiCompany.setName("創(chuàng)業(yè)街分店");
// 給SBI店新增一款飲品
sbiCompany.addDrink("雪碧");
System.out.println("SBI店: " + sbiCompany);
這時(shí), SBI店的飲品列表是:
SBI店: {名字: '創(chuàng)業(yè)街分店', 飲品: [橙汁, 可樂, 酸梅湯, 雪碧]'}
看著很好, Perfect.
然而, 這時(shí), 表妹打來電話了, 說我光谷店這邊的菜單系統(tǒng)怎么無端多出一款雪碧的飲料啊, 我這沒有提供的啊, 怎么給客戶啊.
小光立馬打印了下光谷店的信息(基于上面的修改):
// 在光谷店的基礎(chǔ)上clone SBI店
Company sbiCompany = ovCompany.clone();
sbiCompany.setName("創(chuàng)業(yè)街分店");
// 給SBI店新增一款飲品
sbiCompany.addDrink("雪碧");
System.out.println("SBI店: " + sbiCompany);
// 打印下光谷店ovCompany
System.out.println("光谷店: " + ovCompany);
果然, 光谷店新增了"雪碧",
SBI店: {名字: '創(chuàng)業(yè)街分店', 飲品: [橙汁, 可樂, 酸梅湯, 雪碧]'}
光谷店: {名字: '光谷軟件園分店', 飲品: [橙汁, 可樂, 酸梅湯, 雪碧]'}
這樣當(dāng)然是不好的咯, 小光只想復(fù)制光谷店的基本流程架構(gòu)過來, 后續(xù)兩個(gè)店的某些方面還是要分開發(fā)展的, 可不能一改俱改啊.
改進(jìn)之路
小光又開始了clone的改進(jìn)之路. 先回頭看下, 小光之前是怎么clone的:
@Override
protected Company clone() {
Company company = null;
try {
company = (Company) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
我們注意到, 這個(gè)clone只是clone了Company, 并沒有clone Company內(nèi)部的引用(ArrayList<String> drinks). 也就是說clone出來的對(duì)象和之前的對(duì)象會(huì)使用同一份drinks列表注1, 這顯然不是小光愿意看到的.
小光也很快想到了解決方案, 改造了clone過程:
@Override
protected Company clone() {
Company company = null;
try {
company = (Company) super.clone();
// 對(duì)于對(duì)象的屬性也加以clone
company.drinks = (ArrayList<String>) this.drinks.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
這次小光不僅clone了Company, 還clone了其屬性值drinks注2.
讓我們來看下小光的成果:
和之前同樣的使用:
// new 光谷店
Company ovCompany = new OpticalValleyCompany();
System.out.println("光谷店: " + ovCompany);
// 在光谷店的基礎(chǔ)上clone SBI店
Company sbiCompany = ovCompany.clone();
sbiCompany.setName("創(chuàng)業(yè)街分店");
// 給SBI店新增一款飲品
sbiCompany.addDrink("雪碧");
System.out.println("SBI店: " + sbiCompany);
System.out.println("光谷店: " + ovCompany);
改造后的結(jié)果:
光谷店: {名字: '光谷軟件園分店', 飲品: [橙汁, 可樂, 酸梅湯]'}
SBI店: {名字: '創(chuàng)業(yè)街分店', 飲品: [橙汁, 可樂, 酸梅湯, 雪碧]'}
光谷店: {名字: '光谷軟件園分店', 飲品: [橙汁, 可樂, 酸梅湯]'}
我們看到, 光谷店不會(huì)因?yàn)镾BI店的改變而改變了.
小光熱干面SBI店試運(yùn)營(yíng)正式開始, 歡迎大家光臨咯...
故事之后
我們?cè)诠适轮卸啻翁岬搅薱lone, 原型. 沒錯(cuò), 這個(gè)就是原型模式. 照例, 我們來梳理下類之間的關(guān)系, 相對(duì)簡(jiǎn)單:

原型模式:
通過原型對(duì)象實(shí)例, 使用clone的方式來快速創(chuàng)建一個(gè)新的(與原型對(duì)象實(shí)例一致的)對(duì)象實(shí)例.
由于原型模式較為通用, 且相對(duì)簡(jiǎn)單, Java中的最基類Object已經(jīng)提供了clone方法, 來方便我們復(fù)制出新的對(duì)象實(shí)例.
擴(kuò)展閱讀一
上述故事中, 我們?cè)谀承┘恿?sup>注1, 注2的標(biāo)簽. 這就是我們今天的擴(kuò)展閱讀一要注意的內(nèi)容:
注1 淺拷貝
注2 深拷貝
其實(shí), 跟隨故事我們也大致了解了淺拷貝和深拷貝的區(qū)別:
- 淺拷貝對(duì)于要克隆的對(duì)象, 會(huì)復(fù)制其基本數(shù)據(jù)類型(包括String)的屬性(本例中的name屬性)的值給新的對(duì)象. 而對(duì)于非基本數(shù)據(jù)類型的屬性(本例中的drinks), 僅僅復(fù)制一份引用給新產(chǎn)生的對(duì)象, 即新產(chǎn)生的對(duì)象和原始對(duì)象中的非基本數(shù)據(jù)類型的屬性都指向的是同一個(gè)對(duì)象.
- 深拷貝 對(duì)于要克隆的對(duì)象, clone出的非基本數(shù)據(jù)類型的屬性(要求屬性也實(shí)現(xiàn)了Cloneable接口, ArrayList就已經(jīng)自帶實(shí)現(xiàn)了)不再是和原對(duì)象指向同一個(gè)對(duì)象了, 而是一個(gè)新的clone出來的屬性對(duì)象實(shí)例.
如下:

擴(kuò)展閱讀二
如果我們查看java源碼, 可以發(fā)現(xiàn), 我們調(diào)用的clone()方法是Object對(duì)象的. 而不是Cloneable接口的. 那么我們?yōu)槭裁匆獙?shí)現(xiàn)Cloneable接口呢? 不識(shí)閑Cloneable接口可否調(diào)用Object的clone()方法呢?
我們先來看下Cloneable接口的源碼:
public interface Cloneable {
}
發(fā)現(xiàn)其中并沒有任何方法. 幸運(yùn)的是Java源碼的java doc注釋足夠清晰:
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*/
大體我們可以理解幾點(diǎn):
- Cloneable可以看著是一個(gè)標(biāo)識(shí), 實(shí)現(xiàn)了改接口的類才能合法地調(diào)用其從Object類中繼承而來的clone()方法.
- 如果沒有實(shí)現(xiàn)Cloneable接口而調(diào)用clone()方法, 會(huì)觸發(fā)CloneNotSupportedException異常.
- 實(shí)現(xiàn)Cloneable接口的類應(yīng)當(dāng)重寫Object的clone()方法.
擴(kuò)展閱讀三
原型模式也是一種創(chuàng)建型的設(shè)計(jì)模式, 一般會(huì)結(jié)合工廠模式一起使用, 來構(gòu)建對(duì)象. 本例中就不擴(kuò)展了.
好了, 小光熱干面創(chuàng)業(yè)街分店開張啦, 吃熱干面贈(zèng)雪碧了, 歡迎大家光臨, 歡迎大家關(guān)注.