原型模式

原型模式

所謂的原型模式敢朱,無非就是從一個對象再創(chuàng)建另一個可定制的對象,而且不需要知道任何創(chuàng)建的細節(jié)栖茉。所謂的原型模式,其實質(zhì)就是編程需要中的克隆技術(shù)孵延,以某個對象為原型吕漂,復(fù)制出新的對象。只是需要注意深復(fù)制與淺復(fù)制的問題尘应。

原型模式(Prototype Pattern)是用于創(chuàng)建重復(fù)的對象惶凝,同時又能保證性能吼虎。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式苍鲜。

這種模式是實現(xiàn)了一個原型接口思灰,該接口用于創(chuàng)建當前對象的克隆。當直接創(chuàng)建對象的代價比較大時混滔,則采用這種模式洒疚。例如,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建坯屿。我們可以緩存該對象油湖,在下一個請求時返回它的克隆,在需要的時候更新數(shù)據(jù)庫领跛,以此來減少數(shù)據(jù)庫調(diào)用乏德。

介紹

意圖:用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象吠昭。

主要解決:在運行期建立和刪除原型喊括。

何時使用: 1、當一個系統(tǒng)應(yīng)該獨立于它的產(chǎn)品創(chuàng)建矢棚,構(gòu)成和表示時瘾晃。 2、當要實例化的類是在運行時刻指定時幻妓,例如蹦误,通過動態(tài)裝載。 3肉津、為了避免創(chuàng)建一個與產(chǎn)品類層次平行的工廠類層次時强胰。 4、當一個類的實例只能有幾個不同狀態(tài)組合中的一種時妹沙。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實例化該類更方便一些偶洋。

如何解決:利用已有的一個原型對象,快速地生成和原型對象一樣的實例距糖。

關(guān)鍵代碼: 1玄窝、實現(xiàn)克隆操作,在 JAVA 繼承 Cloneable悍引,重寫 clone()恩脂,在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來實現(xiàn)對象的淺拷貝或通過序列化的方式來實現(xiàn)深拷貝。 2趣斤、原型模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關(guān)系俩块,它同樣要求這些"易變類"擁有穩(wěn)定的接口。

應(yīng)用實例: 1、細胞分裂玉凯。 2势腮、JAVA 中的 Object clone() 方法。

優(yōu)點: 1漫仆、性能提高捎拯。 2、逃避構(gòu)造函數(shù)的約束盲厌。

缺點: 1署照、配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難狸眼,但對于已有的類不一定很容易藤树,特別當一個類引用不支持串行化的間接對象浴滴,或者引用含有循環(huán)結(jié)構(gòu)的時候拓萌。 2、必須實現(xiàn) Cloneable 接口升略。

使用場景: 1微王、資源優(yōu)化場景。 2品嚣、類初始化需要消化非常多的資源炕倘,這個資源包括數(shù)據(jù)、硬件資源等翰撑。 3罩旋、性能和安全要求的場景。 4眶诈、通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準備或訪問權(quán)限涨醋,則可以使用原型模式。 5逝撬、一個對象多個修改者的場景浴骂。 6、一個對象需要提供給其他對象訪問宪潮,而且各個調(diào)用者可能都需要修改其值時溯警,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用。 7狡相、在實際項目中梯轻,原型模式很少單獨出現(xiàn),一般是和工廠方法模式一起出現(xiàn)尽棕,通過 clone 的方法創(chuàng)建一個對象檩淋,然后由工廠方法提供給調(diào)用者。原型模式已經(jīng)與 Java 融為渾然一體,大家可以隨手拿來使用蟀悦。

注意事項:與通過對一個類進行實例化來構(gòu)造新對象不同的是媚朦,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的。淺拷貝實現(xiàn) Cloneable日戈,重寫询张,深拷貝是通過實現(xiàn) Serializable 讀取二進制流。

應(yīng)用場景

你一定遇到過大篇幅getter浙炼、setter賦值的場景份氧。例如這樣的代碼:


public class ContentDataDto {
    /**
     * 內(nèi)容主鍵
     */
    private Integer contentId;
    /**
     * Item寬度(以單元格為單位)
     */
    private Integer itemWidth;

    /**
     * Item高度(以單元格為單位)
     */
    private Integer itemHeight;

    /**
     * Item的(x軸)橫坐標(以單元格為單位)
     */
    private Integer itemx;

    /**
     * Item的(y軸)縱坐標(以單元格為單位)
     */
    private Integer itemy;

    /**
     * 布局ID
     */
    private Integer layoutId;
    /**
     * 業(yè)務(wù)數(shù)據(jù)id
     */
    private String bsid;

    /**
     * 標題(可包含簡介等)
     */
    private String title;
    
    /**
     * 內(nèi)容名稱
     */
    private String tag;

    /**
     * 順序
     */
    private Integer prior;
    /**
     * 數(shù)據(jù)來源:0-后臺手動添加;1-同步請求添加
     */
    private Integer dataSource;
    /**
     * 內(nèi)容評分
     */
    private String score;
    ...
}

代碼非常工整弯屈,命名非常規(guī)范蜗帜,注釋也寫的很全面,大家覺得這樣的代碼優(yōu)雅嗎资厉?我認為厅缺,這樣的代碼屬于純體力勞動。那么原型模式宴偿,能幫助我們解決這樣的問題湘捎。原型模式(Prototype Pattern)是指原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象窄刘。
原型模式主要適用于以下場景:
1窥妇、類初始化消耗資源較多。
2娩践、new產(chǎn)生的一個對象需要非常繁瑣的過程(數(shù)據(jù)準備活翩、訪問權(quán)限等)
3、構(gòu)造函數(shù)比較復(fù)雜翻伺。
4材泄、循環(huán)體中生產(chǎn)大量對象時。
在Spring中穆趴,原型模式應(yīng)用得非常廣泛脸爱。例如scope=“prototype”,在我們經(jīng)常用的JSON.parseObject()也是一種原型模式未妹。下面簿废,我們來看看原型模式類結(jié)構(gòu)圖:

圖片.png

簡單克隆

一個標準的原型模式代碼,應(yīng)該是這樣設(shè)計的络它。先創(chuàng)建原型Prototype接口:

public interface Prototype{
    Prototype clone();
}

創(chuàng)建具體需要克隆的對象ConcretePrototype :

public class ConcretePrototypeA implements Prototype {

    private int age;
    private String name;
    private List hobbies;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List getHobbies() {
        return hobbies;
    }

    public void setHobbies(List hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public ConcretePrototypeA clone() {
        ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
        concretePrototype.setAge(this.age);
        concretePrototype.setName(this.name);
        concretePrototype.setHobbies(this.hobbies);
        return concretePrototype;
    }

}

創(chuàng)建Client對象:

public class Client {

    private Prototype prototype;

    public Client(Prototype prototype){
        this.prototype = prototype;
    }
    public Prototype startClone(Prototype concretePrototype){
        return (Prototype)concretePrototype.clone();
    }

}

測試代碼:

public class PrototypeTest {

    public static void main(String[] args) {

        // 創(chuàng)建一個具體的需要克隆的對象
        ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
        // 填充屬性族檬,方便測試
        concretePrototype.setAge(18);
        concretePrototype.setName("prototype");
        List hobbies = new ArrayList<String>();
        concretePrototype.setHobbies(hobbies);
        System.out.println(concretePrototype);

        // 創(chuàng)建Client對象,準備開始克隆
        Client client = new Client(concretePrototype);
        ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA) client.startClone(concretePrototype);
        System.out.println(concretePrototypeClone);

        System.out.println("克隆對象中的引用類型地址值:" + concretePrototypeClone.getHobbies());
        System.out.println("原對象中的引用類型地址值:" + concretePrototype.getHobbies());
        System.out.println("對象地址比較:"+(concretePrototypeClone.getHobbies() == concretePrototype.getHobbies()));


    }
}

運行結(jié)果:

ConcretePrototypeA@1b6d3586
ConcretePrototypeA@4554617c
克隆對象中的引用類型地址值:[]
原對象中的引用類型地址值:[]
對象地址比較:true

Process finished with exit code 0

從測試結(jié)果看出hobbies的引用地址是相同的化戳,意味著復(fù)制的不是值单料,而是引用的地址埋凯。這樣的話,如果我們修改任意一個對象中的屬性值扫尖,concretePrototype 和concretePrototypeCone的hobbies值都會改變白对。這就是我們常說的淺克隆。只是完整復(fù)制了值類型數(shù)據(jù)换怖,沒有賦值引用對象甩恼。換言之,所有的引用對象仍然指向原來的對象沉颂,顯然不是我們想要的結(jié)果条摸。下面我們來看深度克隆繼續(xù)改造。

深度克隆

我們換一個場景铸屉,大家都知道齊天大圣钉蒲。首先它是一只猴子,有七十二般變化彻坛,把一根毫毛就可以吹出千萬個潑猴顷啼,手里還拿著金箍棒,金箍棒可以變大變小小压。這就是我們耳熟能詳?shù)脑湍J降慕?jīng)典體現(xiàn)线梗。
創(chuàng)建原型猴子Monkey類:

public class Monkey {
    public int height;
    public int weight;
    public Date birthday;

}

創(chuàng)建引用對象金箍棒Jingubang類:

public class JinGuBang implements Serializable {
    public float h = 100;
    public float d = 10;

    public void big(){
        this.d *= 2;
        this.h *= 2;
    }

    public void small(){
        this.d /= 2;
        this.h /= 2;
    }
}

創(chuàng)建具體的對象齊天大圣QiTianDaSheng類:

public class QiTianDaSheng extends Monkey implements Cloneable,Serializable {

    public JinGuBang jinGuBang;

    public  QiTianDaSheng(){
        //只是初始化
        this.birthday = new Date();
        this.jinGuBang = new JinGuBang();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return this.deepClone();
    }


    public Object deepClone(){
        try{

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            QiTianDaSheng copy = (QiTianDaSheng)ois.readObject();
            copy.birthday = new Date();
            return copy;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }

    }


    public QiTianDaSheng shallowClone(QiTianDaSheng target){

        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        qiTianDaSheng.height = target.height;
        qiTianDaSheng.weight = target.height;

        qiTianDaSheng.jinGuBang = target.jinGuBang;
        qiTianDaSheng.birthday = new Date();
        return  qiTianDaSheng;
    }


}

測試代碼:

public class DeepCloneTest {

    public static void main(String[] args) {

        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        try {
            QiTianDaSheng clone = (QiTianDaSheng)qiTianDaSheng.clone();
            System.out.println("深克乱凇:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang));
        } catch (Exception e) {
            e.printStackTrace();
        }

        QiTianDaSheng q = new QiTianDaSheng();
        QiTianDaSheng n = q.shallowClone(q);
        System.out.println("淺克碌∫妗:" + (q.jinGuBang == n.jinGuBang));


    }
}

運行結(jié)果:

深克隆:false
淺克埋觥:true

Process finished with exit code 0

克隆破壞單例模式

如果我們克隆的目標的對象是單例對象蜻牢,那意味著,深克隆就會破壞單例偏陪。實際上防止克隆破壞單例解決思路非常簡單抢呆,禁止深克隆便可。要么你我們的單例類不實現(xiàn)Cloneable接口笛谦;要么我們重寫clone()方法抱虐,在clone方法中返回單例對象即可,具體代碼如下:

@Override
    protected Object clone() throws CloneNotSupportedException {
        return this.deepClone();
    }

Cloneable源碼分析

先看我們常用的ArrayList就實現(xiàn)了Cloneable接口饥脑,來看代碼clone()方法的實現(xiàn):

 public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
一些信息
路漫漫其修遠兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恳邀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灶轰,更是在濱河造成了極大的恐慌谣沸,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笋颤,死亡現(xiàn)場離奇詭異乳附,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門赋除,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阱缓,“玉大人,你說我怎么就攤上這事举农〔绲唬” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵并蝗,是天一觀的道長祭犯。 經(jīng)常有香客問我,道長滚停,這世上最難降的妖魔是什么沃粗? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮键畴,結(jié)果婚禮上最盅,老公的妹妹穿的比我還像新娘。我一直安慰自己起惕,他們只是感情好涡贱,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惹想,像睡著了一般问词。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嘀粱,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天激挪,我揣著相機與錄音,去河邊找鬼锋叨。 笑死垄分,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的娃磺。 我是一名探鬼主播薄湿,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼偷卧!你這毒婦竟也來了豺瘤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涯冠,失蹤者是張志新(化名)和其女友劉穎炉奴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛇更,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡瞻赶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年赛糟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砸逊。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡璧南,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出师逸,到底是詐尸還是另有隱情司倚,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布篓像,位于F島的核電站动知,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏员辩。R本人自食惡果不足惜盒粮,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奠滑。 院中可真熱鬧丹皱,春花似錦、人聲如沸宋税。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杰赛。三九已至呢簸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淆攻,已是汗流浹背阔墩。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工嘿架, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓶珊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓耸彪,卻偏偏與公主長得像伞芹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝉娜,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359