創(chuàng)業(yè)街分店開張啦---原型模式

cover

前情提要

上集講到, 小光請(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):

  1. Cloneable可以看著是一個(gè)標(biāo)識(shí), 實(shí)現(xiàn)了改接口的類才能合法地調(diào)用其從Object類中繼承而來的clone()方法.
  2. 如果沒有實(shí)現(xiàn)Cloneable接口而調(diào)用clone()方法, 會(huì)觸發(fā)CloneNotSupportedException異常.
  3. 實(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)注.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容