設(shè)計(jì)模式《原型模式》

引言

??回顧上一節(jié)我們講的狀態(tài)模式,這節(jié)我們來(lái)講一下原型模式。和原型模式相關(guān)的2個(gè)概念:淺拷貝和深拷貝。

示例地址

??Demo

先上類圖

image

再看定義

??用原型實(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é)

  1. 對(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í)行也是非常正常的了偶房。
  2. final 類型修飾的成員變量不能進(jìn)行深拷貝
  3. 在實(shí)際項(xiàng)目中,原型模式很少單獨(dú)出現(xiàn)憔狞,一般是和工廠方法模式一起出現(xiàn)蝴悉,通過(guò) clone的方法創(chuàng)建一個(gè)對(duì)象,然后由工廠方法提供給調(diào)用者瘾敢。原型模式先產(chǎn)生出一個(gè)包含大量共有信息的類拍冠,然后可以拷貝出副本,修正細(xì)節(jié)信息簇抵,建立了一個(gè)完整的個(gè)性對(duì)象庆杜。

參考博客

??23中設(shè)計(jì)模式之_原型模式(深/淺拷貝)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市碟摆,隨后出現(xiàn)的幾起案子晃财,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件断盛,死亡現(xiàn)場(chǎng)離奇詭異罗洗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)钢猛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門伙菜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人命迈,你說(shuō)我怎么就攤上這事贩绕。” “怎么了壶愤?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵淑倾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我征椒,道長(zhǎng)娇哆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任陕靠,我火速辦了婚禮迂尝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剪芥。我一直安慰自己,他們只是感情好琴许,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布税肪。 她就那樣靜靜地躺著,像睡著了一般榜田。 火紅的嫁衣襯著肌膚如雪益兄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天箭券,我揣著相機(jī)與錄音净捅,去河邊找鬼。 笑死辩块,一個(gè)胖子當(dāng)著我的面吹牛蛔六,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播废亭,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼国章,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了豆村?” 一聲冷哼從身側(cè)響起液兽,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掌动,沒想到半個(gè)月后四啰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宁玫,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年柑晒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撬统。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敦迄,死狀恐怖恋追,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情罚屋,我是刑警寧澤苦囱,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布,位于F島的核電站脾猛,受9級(jí)特大地震影響撕彤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猛拴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一羹铅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愉昆,春花似錦职员、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至芳室,卻和暖如春专肪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背堪侯。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工嚎尤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伍宦。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓芽死,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親雹拄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子收奔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容