Android常見設(shè)計模式八:原型模式

對于開發(fā)人員來說,設(shè)計模式有時候就是一道坎狰域,但是設(shè)計模式又非常有用媳拴,過了這道坎黄橘,它可以讓你水平提高一個檔次。而在android開發(fā)中禀挫,必要的了解一些設(shè)計模式又是必須的,因為設(shè)計模式在Android源碼中拓颓,可以說是無處不在语婴。對于想系統(tǒng)的學(xué)習(xí)設(shè)計模式的同學(xué),這里推薦一本書驶睦,《大話設(shè)計模式》砰左。


Android常用設(shè)計模式系列:

面向?qū)ο蟮幕A(chǔ)特征
面向?qū)ο蟮脑O(shè)計原則
單例模式
模板模式
適配器模式
工廠模式
代理模式
原型模式
策略模式
Build模式
觀察者模式
裝飾者模式
中介模式
門面模式


原型模式

原型模式是非常常見的設(shè)計模式之一,寫個筆記场航,記錄一下我的學(xué)習(xí)過程和心得缠导。

首先了解一些原型模式的定義。

用原型實例指定創(chuàng)建對象的種類溉痢,并通過拷貝這些原型創(chuàng)建新的對象僻造。

又是一個看了讓人一臉懵逼的定義,不過沒關(guān)系孩饼,我們看下面的描述的非常清楚啦髓削。

首先我們定義一個Person類

    public class Person{
        private String name;
        private int age;
        private double height;
        private double weight;
        public Person(){
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public double getHeight() {
            return height;
        }
        public void setHeight(double height) {
            this.height = height;
        }
        public double getWeight() {
            return weight;
        }
        public void setWeight(double weight) {
            this.weight = weight;
        }
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", height=" + height +
                    ", weight=" + weight +
                    '}';
        }
    }

要實現(xiàn)原型模式,只需要按照下面的幾個步驟去實現(xiàn)即可镀娶。

  • 實現(xiàn)Cloneable接口
public class Person implements Cloneable{
 } 
  • 重寫Object的clone方法
@Override
public Object clone(){
  return null;
}
  • 實現(xiàn)clone方法中的拷貝邏輯
    @Override
    public Object clone(){
        Person person=null;
        try {
            person=(Person)super.clone();
            person.name=this.name;
            person.weight=this.weight;
            person.height=this.height;
            person.age=this.age;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }

測試一下

    public class Main {
        public static void main(String [] args){
            Person p=new Person();
            p.setAge(18);
            p.setName("張三");
            p.setHeight(178);
            p.setWeight(65);
            System.out.println(p);
            Person p1= (Person) p.clone();
            System.out.println(p1);
            p1.setName("李四");
            System.out.println(p);
            System.out.println(p1);
        }
    }

輸出結(jié)果如下

Person{name=’張三’, age=18, height=178.0, weight=65.0}
Person{name=’張三’, age=18, height=178.0, weight=65.0}
Person{name=’張三’, age=18, height=178.0, weight=65.0}
Person{name=’李四’, age=18, height=178.0, weight=65.0}

試想一下立膛,兩個不同的人,除了姓名不一樣梯码,其他三個屬性都一樣宝泵,用原型模式進行拷貝就會顯得異常簡單,這也是原型模式的應(yīng)用場景之一轩娶。

一個對象需要提供給其他對象訪問儿奶,而且各個調(diào)用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用鳄抒,即保護性拷貝廓握。

但是假設(shè)Person類里還有一個屬性叫興趣愛好,是一個List集合嘁酿,就像這樣子

    private ArrayList<String> hobbies=new ArrayList<String>();
    public ArrayList<String> getHobbies() {
        return hobbies;
    }
    public void setHobbies(ArrayList<String> hobbies) {
        this.hobbies = hobbies;
    }

在進行拷貝的時候要格外注意隙券,如果你直接按之前的代碼那樣拷貝

    @Override
    public Object clone(){
        Person person=null;
        try {
            person=(Person)super.clone();
            person.name=this.name;
            person.weight=this.weight;
            person.height=this.height;
            person.age=this.age;
            person.hobbies=this.hobbies;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }

會帶來一個問題

使用測試代碼進行測試

    public class Main {
        public static void main(String [] args){
            Person p=new Person();
            p.setAge(18);
            p.setName("張三");
            p.setHeight(178);
            p.setWeight(65);
            ArrayList <String> hobbies=new ArrayList<String>();
            hobbies.add("籃球");
            hobbies.add("編程");
            hobbies.add("長跑");
            p.setHobbies(hobbies);
            System.out.println(p);
            Person p1= (Person) p.clone();
            System.out.println(p1);
            p1.setName("李四");
            p1.getHobbies().add("游泳");
            System.out.println(p);
            System.out.println(p1);
        }
    }

我們拷貝了一個對象,并添加了一個興趣愛好進去闹司,看下打印結(jié)果

Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑, 游泳]}
Person{name=’李四’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑, 游泳]}

你會發(fā)現(xiàn)原來的對象的hobby也發(fā)生了變換娱仔。

其實導(dǎo)致這個問題的本質(zhì)原因是我們只進行了淺拷貝,也就是只拷貝了引用游桩,最終兩個對象指向的引用是同一個牲迫,一個發(fā)生變化另一個也會發(fā)生變換耐朴,顯然解決方法就是使用深拷貝。

    @Override
    public Object clone(){
        Person person=null;
        try {
            person=(Person)super.clone();
            person.name=this.name;
            person.weight=this.weight;
            person.height=this.height;
            person.age=this.age;
            person.hobbies=(ArrayList<String>)this.hobbies.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }

注意person.hobbies=(ArrayList)this.hobbies.clone();盹憎,不再是直接引用而是進行了一份拷貝筛峭。再運行一下,就會發(fā)現(xiàn)原來的對象不會再發(fā)生變化了陪每。

Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑]}
Person{name=’張三’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑]}
Person{name=’李四’, age=18, height=178.0, weight=65.0, hobbies=[籃球, 編程, 長跑, 游泳]}

其實有時候我們會更多的看到原型模式的另一種寫法影晓。

  • 在clone函數(shù)里調(diào)用構(gòu)造函數(shù),構(gòu)造函數(shù)的入?yún)⑹窃擃悓ο蟆?/li>
@Override
public Object clone(){
  return new Person(this);
}
  • 在構(gòu)造函數(shù)中完成拷貝邏輯
    public Person(Person person){
        this.name=person.name;
        this.weight=person.weight;
        this.height=person.height;
        this.age=person.age;
        this.hobbies= new ArrayList<String>(hobbies);
    }

其實都差不多檩禾,只是寫法不一樣挂签。

廣泛應(yīng)用

現(xiàn)在來挖挖android中的原型模式。

先看Bundle類盼产,該類實現(xiàn)了Cloneable接口

    public Object clone() {
        return new Bundle(this);
    }
    public Bundle(Bundle b) {
        super(b);
        mHasFds = b.mHasFds;
        mFdsKnown = b.mFdsKnown;
    }

然后是Intent類饵婆,該類也實現(xiàn)了Cloneable接口

    @Override
    public Object clone() {
        return new Intent(this);
    }
    public Intent(Intent o) {
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;
        this.mFlags = o.mFlags;
        this.mContentUserHint = o.mContentUserHint;
        if (o.mCategories != null) {
            this.mCategories = new ArraySet<String>(o.mCategories);
        }
        if (o.mExtras != null) {
            this.mExtras = new Bundle(o.mExtras);
        }
        if (o.mSourceBounds != null) {
            this.mSourceBounds = new Rect(o.mSourceBounds);
        }
        if (o.mSelector != null) {
            this.mSelector = new Intent(o.mSelector);
        }
        if (o.mClipData != null) {
            this.mClipData = new ClipData(o.mClipData);
        }
    }

用法也顯得十分簡單,一旦我們要用的Intent與現(xiàn)有的一個Intent很多東西都是一樣的戏售,那我們就可以直接拷貝現(xiàn)有的Intent侨核,再修改不同的地方,便可以直接使用灌灾。

Uri uri = Uri.parse("smsto:10086");    
Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri);    
shareIntent.putExtra("sms_body", "hello");    
 Intent intent = (Intent)shareIntent.clone() ;
startActivity(intent);

網(wǎng)絡(luò)請求中一個最常見的開源庫OkHttp中芹关,也應(yīng)用了原型模式。它就在OkHttpClient這個類中紧卒,它實現(xiàn)了Cloneable接口

    /** Returns a shallow copy of this OkHttpClient. */
    @Override
    public OkHttpClient clone() {
        return new OkHttpClient(this);
    }
    private OkHttpClient(OkHttpClient okHttpClient) {
        this.routeDatabase = okHttpClient.routeDatabase;
        this.dispatcher = okHttpClient.dispatcher;
        this.proxy = okHttpClient.proxy;
        this.protocols = okHttpClient.protocols;
        this.connectionSpecs = okHttpClient.connectionSpecs;
        this.interceptors.addAll(okHttpClient.interceptors);
        this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
        this.proxySelector = okHttpClient.proxySelector;
        this.cookieHandler = okHttpClient.cookieHandler;
        this.cache = okHttpClient.cache;
        this.internalCache = cache != null ? cache.internalCache : okHttpClient.internalCache;
        this.socketFactory = okHttpClient.socketFactory;
        this.sslSocketFactory = okHttpClient.sslSocketFactory;
        this.hostnameVerifier = okHttpClient.hostnameVerifier;
        this.certificatePinner = okHttpClient.certificatePinner;
        this.authenticator = okHttpClient.authenticator;
        this.connectionPool = okHttpClient.connectionPool;
        this.network = okHttpClient.network;
        this.followSslRedirects = okHttpClient.followSslRedirects;
        this.followRedirects = okHttpClient.followRedirects;
        this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
        this.connectTimeout = okHttpClient.connectTimeout;
        this.readTimeout = okHttpClient.readTimeout;
        this.writeTimeout = okHttpClient.writeTimeout;
    }

正如開頭的注釋Returns a shallow copy of this OkHttpClient侥衬,該clone方法返回了一個當(dāng)前對象的淺拷貝對象。

至于其他框架中的原型模式跑芳,請讀者自行發(fā)現(xiàn)轴总。

總結(jié)

總結(jié)一下觀察者模式的有確定及應(yīng)用場景。

優(yōu)點

  1. 使用原型模型創(chuàng)建一個對象比直接new一個對象更有效率博个,因為它直接操作內(nèi)存中的二進制流怀樟,特別是復(fù)制大對象時,性能的差別非常明顯盆佣。
  2. 隱藏了制造新實例的復(fù)雜性往堡,使得創(chuàng)建對象就像我們在編輯文檔時的復(fù)制粘貼一樣簡單。

缺點

  1. 由于使用原型模式復(fù)制對象時不會調(diào)用類的構(gòu)造方法共耍,所以原型模式無法和單例模式組合使用虑灰,因為原型類需要將clone方法的作用域修改為public類型,那么單例模式的條件就無法滿足了痹兜。
  2. 使用原型模式時不能有final對象穆咐。
  3. Object類的clone方法只會拷貝對象中的基本數(shù)據(jù)類型,對于數(shù)組,引用對象等只能另行拷貝对湃。這里涉及到深拷貝和淺拷貝的概念崖叫。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拍柒,隨后出現(xiàn)的幾起案子心傀,更是在濱河造成了極大的恐慌,老刑警劉巖拆讯,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脂男,死亡現(xiàn)場離奇詭異,居然都是意外死亡往果,警方通過查閱死者的電腦和手機疆液,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門一铅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陕贮,“玉大人,你說我怎么就攤上這事潘飘“怪” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵卜录,是天一觀的道長戈擒。 經(jīng)常有香客問我,道長艰毒,這世上最難降的妖魔是什么筐高? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮丑瞧,結(jié)果婚禮上柑土,老公的妹妹穿的比我還像新娘。我一直安慰自己绊汹,他們只是感情好稽屏,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著西乖,像睡著了一般狐榔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上获雕,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天薄腻,我揣著相機與錄音,去河邊找鬼届案。 笑死被廓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萝玷。 我是一名探鬼主播嫁乘,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼昆婿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蜓斧?” 一聲冷哼從身側(cè)響起仓蛆,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挎春,沒想到半個月后看疙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡直奋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年能庆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脚线。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡搁胆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出邮绿,到底是詐尸還是另有隱情渠旁,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布船逮,位于F島的核電站顾腊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挖胃。R本人自食惡果不足惜杂靶,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酱鸭。 院中可真熱鬧吗垮,春花似錦、人聲如沸凛辣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扁誓。三九已至防泵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝗敢,已是汗流浹背捷泞。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寿谴,地道東北人锁右。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咏瑟。 傳聞我的和親對象是個殘疾皇子拂到,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

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

  • 對于開發(fā)人員來說,設(shè)計模式有時候就是一道坎码泞,但是設(shè)計模式又非常有用兄旬,過了這道坎,它可以讓你水平提高一個檔次余寥。而在a...
    WANKUN閱讀 256評論 0 2
  • 說在前頭~ 看完能動動小手點個心么宋舷?由衷感謝绪撵。 對于開發(fā)人員來說,設(shè)計模式有時候就是一道坎祝蝠,但是設(shè)計模式又非常有...
    S_ZY閱讀 3,138評論 3 60
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5音诈? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 27,453評論 1 45
  • 定義 原型模式屬于對象的創(chuàng)建模式续膳。通過給出一個原型對象來指明所有創(chuàng)建的對象的類型改艇,然后用復(fù)制這個原型對象的辦法創(chuàng)建...
    暮染1閱讀 298評論 0 0
  • 20- 枚舉,枚舉原始值,枚舉相關(guān)值,switch提取枚舉關(guān)聯(lián)值 Swift枚舉: Swift中的枚舉比OC中的枚...
    iOS_恒仔閱讀 2,256評論 1 6