【設(shè)計(jì)模式(四)】原型模式

個(gè)人學(xué)習(xí)筆記分享萝招,當(dāng)前能力有限,請(qǐng)勿貶低存捺,菜鳥互學(xué)槐沼,大佬繞道

如有勘誤,歡迎指出和討論捌治,本文后期也會(huì)進(jìn)行修正和補(bǔ)充


前言

原型模式是一種創(chuàng)建型設(shè)計(jì)模式母赵, 使你能夠復(fù)制已有對(duì)象, 而又無需使代碼依賴它們所屬的類具滴。

原型(Prototype)模式的定義如下:用一個(gè)已經(jīng)創(chuàng)建的實(shí)例作為原型凹嘲,通過復(fù)制該原型對(duì)象來創(chuàng)建一個(gè)和原型相同或相似的新對(duì)象。在這里构韵,原型實(shí)例指定了要?jiǎng)?chuàng)建的對(duì)象的種類周蹭。用這種方式創(chuàng)建對(duì)象非常高效,根本無須知道對(duì)象創(chuàng)建的細(xì)節(jié)疲恢。

當(dāng)直接創(chuàng)建對(duì)象的代價(jià)比較大時(shí)凶朗,則采用這種模式。


1.介紹

使用目的:已知原型實(shí)例的情況下显拳,可以獲得相同的實(shí)例對(duì)象

使用時(shí)機(jī):需要?jiǎng)討B(tài)的生成和刪除實(shí)例模型

解決問題:動(dòng)態(tài)的創(chuàng)建和刪除實(shí)例

實(shí)現(xiàn)方法:實(shí)現(xiàn)Cloneable類的clone()方法

使用場(chǎng)景

  • 通過new一個(gè)對(duì)象需要極其繁瑣的數(shù)據(jù)準(zhǔn)備或者權(quán)限棚愤,那么推薦使用原型模式
  • 對(duì)象初始化需要消耗大量資源的時(shí)候,從舊的對(duì)象進(jìn)行克隆出新對(duì)象杂数,即可不必重復(fù)初始化
  • 一個(gè)對(duì)象可能有多個(gè)修改者宛畦,那么可以克隆出去多份新對(duì)象供其使用

應(yīng)用實(shí)例:

  • 細(xì)胞分裂
  • JAVA 中的 Object clone() 方法

優(yōu)點(diǎn)

  • 性能提高,構(gòu)建新的對(duì)象只需要拷貝舊的對(duì)象即可
  • 逃避構(gòu)造函數(shù)的約束揍移,根本不經(jīng)過構(gòu)造函數(shù)

缺點(diǎn)

  • 配備克隆方法需要對(duì)類的功能進(jìn)行通盤考慮次和,這對(duì)于全新的類不是很難,但對(duì)于已有的類不一定很容易那伐,特別當(dāng)一個(gè)類引用不支持串行化的間接對(duì)象踏施,或者引用含有循環(huán)結(jié)構(gòu)的時(shí)候
  • 類必須實(shí)現(xiàn) Cloneable接口。

注意事項(xiàng):既然是拷貝罕邀,那么必須有源對(duì)象才能實(shí)現(xiàn)畅形,否則還是得構(gòu)建一個(gè)全新的對(duì)象


分類:通過拷貝的方法和其內(nèi)容,分為淺拷貝和深拷貝

  • 淺拷貝:只拷貝源對(duì)象的基本數(shù)據(jù)诉探,而不拷貝容器日熬,引用等等,一般實(shí)現(xiàn)Cloneable類并重寫clone()方法
  • 深拷貝:拷貝源對(duì)象的一切阵具,包括數(shù)據(jù)、容器、引用等等豺旬,一般通過實(shí)現(xiàn) Serializable讀取二進(jìn)制流,直接復(fù)制出新對(duì)象揣炕,也可通過其他方式實(shí)現(xiàn)


2.實(shí)現(xiàn)方案

深拷貝或者淺拷貝是結(jié)果,而非簡(jiǎn)單的由方案決定东跪,比如在clone方法中拷貝全部?jī)?nèi)容畸陡,也可以達(dá)到深拷貝的效果

請(qǐng)注意,示例中修正新對(duì)象僅為了測(cè)試虽填,實(shí)際應(yīng)用中請(qǐng)視情況處理丁恭,理論上應(yīng)當(dāng)保持新舊對(duì)象盡可能一致

2.1.方案1:實(shí)現(xiàn)Cloneable類

實(shí)現(xiàn)Cloneable類并重寫clone()方法即可,在該方法中設(shè)定好需要拷貝的內(nèi)容斋日,調(diào)用源對(duì)象中的該方法即可獲得新的對(duì)象

步驟

  1. 定義一個(gè)實(shí)現(xiàn)了Cloneable接口的抽象類牲览,并實(shí)現(xiàn)clone()方法

    abstract class Animal implements Cloneable {
        protected String type;
        protected List<String> typeSet = new ArrayList<>();
    
        public Animal(String type) {
            this.type = type;
            typeSet.add(type);
        }
    
        void say() {
            System.out.println("myType is " + type);
            System.out.println("myTypeSet is " + String.join(",", typeSet));
        }
    
        public Animal clone() {
            Animal clone = null;
            try {
                //克隆對(duì)象
                clone = (Animal) super.clone();
                //克隆對(duì)象里的復(fù)雜對(duì)象,若不拷貝則會(huì)使用源對(duì)象里的復(fù)雜對(duì)象
                //clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone();
    
                //修正新對(duì)象
                clone.type = type + "Cloned";
                clone.typeSet.add(clone.type);
    
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return clone;
        }
    }
    

    這里我們?cè)诳寺r(shí)恶守,會(huì)對(duì)新對(duì)象進(jìn)行修正第献,而其余內(nèi)容保持與源對(duì)象一致

    復(fù)雜對(duì)象后面進(jìn)行測(cè)試

  2. 定義實(shí)體類,實(shí)現(xiàn)抽象類兔港,簡(jiǎn)單易懂

    class Dog extends Animal {
    
        public Dog() {
            super("dog");
        }
    
        @Override
        void say() {
            super.say();
            System.out.println("汪庸毫!");
        }
    }
    
    class Cat extends Animal {
    
        public Cat() {
            super("cat");
        }
    
        @Override
        void say() {
            super.say();
            System.out.println("喵!");
        }
    }
    
  3. 定義數(shù)據(jù)源初始化和調(diào)用方法

    private Map<String, Animal> animalMap = new HashMap<>();
    
    public void initAnimal() {
        Dog dog = new Dog();
        animalMap.put("dog", dog);
        Cat cat = new Cat();
        animalMap.put("cat", cat);
    }
    
    public Animal getAnimal(String type) {
        if (animalMap.containsKey(type)) {
            return animalMap.get(type).clone();
        } else {
            return null;
        }
    }
    

    initAnimal()方法中初始化出兩個(gè)對(duì)象衫樊,并將其存儲(chǔ)入map

    getAnimal()方法中取出對(duì)象的克隆

  4. 測(cè)試調(diào)用

        public static void main(String[] args) {
            //初始化
            initAnimal();
    
            //第一輪測(cè)試
            System.out.println("test turn 1:");
            Animal dog1 = getAnimal("dog");
            Animal cat1 = getAnimal("cat");
            dog1.say();
            cat1.say();
    
            //第二輪測(cè)試
            System.out.println("test turn 2:");
            Animal dog2 = dog1.clone();
            dog2.say();
    
            //第三輪測(cè)試
            System.out.println("test turn 3:");
            dog1.say();
            System.out.println("clone equals:" + Objects.equals(dog1.typeSet, dog2.typeSet));
        }
    


完整代碼

package com.company.clone;

import java.util.*;

class Animal implements Cloneable {
    protected String type;
    protected List<String> typeSet = new ArrayList<>();

    public Animal(String type) {
        this.type = type;
        typeSet.add(type);
    }

    void say() {
        System.out.println("myType is " + type);
        System.out.println("myTypeSet is " + String.join(",", typeSet));
    }

    public Animal clone() {
        Animal clone = null;
        try {
            //克隆對(duì)象
            clone = (Animal) super.clone();
            //克隆對(duì)象里的復(fù)雜對(duì)象飒赃,若不拷貝則會(huì)使用源對(duì)象里的復(fù)雜對(duì)象
            //clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone();

            //修正新對(duì)象
            clone.type = type + "Cloned";
            clone.typeSet.add(clone.type);

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

class Dog extends Animal {

    public Dog() {
        super("dog");
    }

    @Override
    void say() {
        super.say();
        System.out.println("汪!");
    }
}

class Cat extends Animal {

    public Cat() {
        super("cat");
    }

    @Override
    void say() {
        super.say();
        System.out.println("喵科侈!");
    }
}

public class CloneTest {
    public static void main(String[] args) {
        //初始化
        initAnimal();

        //第一輪測(cè)試
        System.out.println("test turn 1:");
        Animal dog1 = getAnimal("dog");
        Animal cat1 = getAnimal("cat");
        dog1.say();
        cat1.say();

        //第二輪測(cè)試
        System.out.println("test turn 2:");
        Animal dog2 = dog1.clone();
        dog2.say();

        //第三輪測(cè)試
        System.out.println("test turn 3:");
        dog1.say();
        System.out.println("clone equals:" + Objects.equals(dog1.typeSet, dog2.typeSet));
    }

    private static Map<String, Animal> animalMap = new HashMap<>();

    public static void initAnimal() {
        Dog dog = new Dog();
        animalMap.put("dog", dog);
        Cat cat = new Cat();
        animalMap.put("cat", cat);
    }

    public static Animal getAnimal(String type) {
        if (animalMap.containsKey(type)) {
            return animalMap.get(type).clone();
        } else {
            return null;
        }
    }
}

運(yùn)行結(jié)果

image-20201009114117630

分析

  • 第一輪測(cè)試為一次克隆的結(jié)果载佳,type后追加ClonedtypeSet包括2個(gè)數(shù)據(jù)兑徘,分別是舊type和新type
  • 第二輪測(cè)試為二次克隆的結(jié)果刚盈,type后追加兩個(gè)ClonedtypeSet包括3個(gè)數(shù)據(jù)
  • 第三輪仍為一次克隆的結(jié)果挂脑,type后只追加了一個(gè)Cloned,但typeSet卻有3個(gè)數(shù)據(jù)欲侮,與二次克隆是同一個(gè)typeSet

也就是說克隆后崭闲,使用的typeSet是同一個(gè)

==這也就是所說的淺拷貝:只拷貝數(shù)據(jù),而容器威蕉、引用等則直接使用源對(duì)象的刁俭,并不進(jìn)行拷貝==

==如果需要深拷貝,則需要在clone()方法中對(duì)復(fù)雜對(duì)象進(jìn)行復(fù)制==

如果啟用clone()方法中注釋掉的代碼韧涨,運(yùn)行結(jié)果會(huì)變成下面這樣

image-20201009114814380

可以看到現(xiàn)在的typeSet不是同一個(gè)了牍戚,只是拷貝的時(shí)候復(fù)制了里面的內(nèi)容


2.2.方案2:序列化后進(jìn)行復(fù)制

即將對(duì)象以二進(jìn)制完全復(fù)制一份新的侮繁,再轉(zhuǎn)換為對(duì)象,通常使用Serializable和數(shù)據(jù)流一起實(shí)現(xiàn)

步驟

這里僅修改clone()方法如孝,其余步驟與方案1一致

class Animal implements Serializable {
    protected String type;
    protected List<String> typeSet = new ArrayList<>();

    public Animal(String type) {
        this.type = type;
        typeSet.add(type);
    }

    void say() {
        System.out.println("myType is " + type);
        System.out.println("myTypeSet is " + String.join(",", typeSet));
    }

    public Animal clone() {
        Animal clone = null;
        try {
            //將對(duì)象寫到流里
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            //從流里讀回來
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            clone = (Animal) ois.readObject();
            //修正新對(duì)象
            clone.type = type + "Cloned";
            clone.typeSet.add(clone.type);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

將對(duì)象寫入流里宪哩,再讀出來,并強(qiáng)制轉(zhuǎn)換為對(duì)應(yīng)類的對(duì)象即可

運(yùn)行結(jié)果

image-20201009120155318

分析

可以看到結(jié)果是深拷貝第晰,因?yàn)樾聦?duì)象時(shí)由數(shù)據(jù)流轉(zhuǎn)換來的锁孟,復(fù)雜對(duì)象保留的是數(shù)據(jù),而非引用地址茁瘦,那么自然會(huì)構(gòu)造新的


3.后記

實(shí)例中我們預(yù)先初始化所有源對(duì)象品抽,使用map存儲(chǔ),使用的時(shí)候取出對(duì)應(yīng)對(duì)象的克隆體

其實(shí)這就是最常見的應(yīng)用場(chǎng)景甜熔,我們可以快速獲得對(duì)應(yīng)的對(duì)象圆恤,不需要每次都初始化和進(jìn)行構(gòu)造,而對(duì)深拷貝對(duì)象的修改都不會(huì)影響源對(duì)象腔稀,也就可以保證每次的源對(duì)象是相同且純凈的

一次初始化盆昙,之后便可快速得到新的對(duì)象,我們也可以在初始化的時(shí)候進(jìn)行數(shù)據(jù)預(yù)設(shè)等等烧颖,視業(yè)務(wù)情況處理

總之弱左,還是很實(shí)用的對(duì)吧~



作者:Echo_Ye

WX:Echo_YeZ

EMAIL :echo_yezi@qq.com

個(gè)人站點(diǎn):在搭了在搭了。炕淮。拆火。(右鍵 - 新建文件夾)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涂圆,隨后出現(xiàn)的幾起案子们镜,更是在濱河造成了極大的恐慌,老刑警劉巖润歉,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件模狭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡踩衩,警方通過查閱死者的電腦和手機(jī)嚼鹉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驱富,“玉大人锚赤,你說我怎么就攤上這事『峙福” “怎么了线脚?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我浑侥,道長(zhǎng)姊舵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任寓落,我火速辦了婚禮括丁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘零如。我一直安慰自己躏将,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布考蕾。 她就那樣靜靜地躺著祸憋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肖卧。 梳的紋絲不亂的頭發(fā)上蚯窥,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音塞帐,去河邊找鬼拦赠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛葵姥,可吹牛的內(nèi)容都是我干的荷鼠。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼榔幸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼允乐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起削咆,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤牍疏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拨齐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳞陨,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瞻惋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厦滤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歼狼,死狀恐怖馁害,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹂匹,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布凹蜈,位于F島的核電站限寞,受9級(jí)特大地震影響忍啸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜履植,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一计雌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玫霎,春花似錦凿滤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鼻种,卻和暖如春反番,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叉钥。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工罢缸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人投队。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓枫疆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親敷鸦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子息楔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350