Android常見設計模式八:原型模式

對于開發人員來說,設計模式有時候就是一道坎,但是設計模式又非常有用,過了這道坎,它可以讓你水平提高一個檔次。而在android開發中,必要的了解一些設計模式又是必須的,因為設計模式在Android源碼中,可以說是無處不在。對于想系統的學習設計模式的同學,這里推薦一本書,《大話設計模式》。


Android常用設計模式系列:

面向對象的基礎特征
面向對象的設計原則
單例模式
模板模式
適配器模式
工廠模式
代理模式
原型模式
策略模式
Build模式
觀察者模式
裝飾者模式
中介模式
門面模式


原型模式

原型模式是非常常見的設計模式之一,寫個筆記,記錄一下我的學習過程和心得。

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

用原型實例指定創建對象的種類,并通過拷貝這些原型創建新的對象。

又是一個看了讓人一臉懵逼的定義,不過沒關系,我們看下面的描述的非常清楚啦。

首先我們定義一個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 +
                    '}';
        }
    }

要實現原型模式,只需要按照下面的幾個步驟去實現即可。

  • 實現Cloneable接口
public class Person implements Cloneable{
 } 
  • 重寫Object的clone方法
@Override
public Object clone(){
  return null;
}
  • 實現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);
        }
    }

輸出結果如下

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}

試想一下,兩個不同的人,除了姓名不一樣,其他三個屬性都一樣,用原型模式進行拷貝就會顯得異常簡單,這也是原型模式的應用場景之一。

一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用,即保護性拷貝。

但是假設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);
        }
    }

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

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=[籃球, 編程, 長跑, 游泳]}

你會發現原來的對象的hobby也發生了變換。

其實導致這個問題的本質原因是我們只進行了淺拷貝,也就是只拷貝了引用,最終兩個對象指向的引用是同一個,一個發生變化另一個也會發生變換,顯然解決方法就是使用深拷貝。

    @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();,不再是直接引用而是進行了一份拷貝。再運行一下,就會發現原來的對象不會再發生變化了。

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函數里調用構造函數,構造函數的入參是該類對象。
@Override
public Object clone(){
  return new Person(this);
}
  • 在構造函數中完成拷貝邏輯
    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);
    }

其實都差不多,只是寫法不一樣。

廣泛應用

現在來挖挖android中的原型模式。

先看Bundle類,該類實現了Cloneable接口

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

然后是Intent類,該類也實現了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與現有的一個Intent很多東西都是一樣的,那我們就可以直接拷貝現有的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);

網絡請求中一個最常見的開源庫OkHttp中,也應用了原型模式。它就在OkHttpClient這個類中,它實現了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方法返回了一個當前對象的淺拷貝對象。

至于其他框架中的原型模式,請讀者自行發現。

總結

總結一下觀察者模式的有確定及應用場景。

優點

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

缺點

  1. 由于使用原型模式復制對象時不會調用類的構造方法,所以原型模式無法和單例模式組合使用,因為原型類需要將clone方法的作用域修改為public類型,那么單例模式的條件就無法滿足了。
  2. 使用原型模式時不能有final對象。
  3. Object類的clone方法只會拷貝對象中的基本數據類型,對于數組,引用對象等只能另行拷貝。這里涉及到深拷貝和淺拷貝的概念。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • 對于開發人員來說,設計模式有時候就是一道坎,但是設計模式又非常有用,過了這道坎,它可以讓你水平提高一個檔次。而在a...
    WANKUN閱讀 268評論 0 2
  • 說在前頭~ 看完能動動小手點個心么?由衷感謝。 對于開發人員來說,設計模式有時候就是一道坎,但是設計模式又非常有...
    S_ZY閱讀 3,154評論 3 60
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標準。 注意:講述HT...
    kismetajun閱讀 27,582評論 1 45
  • 定義 原型模式屬于對象的創建模式。通過給出一個原型對象來指明所有創建的對象的類型,然后用復制這個原型對象的辦法創建...
    暮染1閱讀 302評論 0 0
  • 20- 枚舉,枚舉原始值,枚舉相關值,switch提取枚舉關聯值 Swift枚舉: Swift中的枚舉比OC中的枚...
    iOS_恒仔閱讀 2,309評論 1 6