Android設計模式

前言

關于面向對象的六大原則請參考前面的博文
csdn上關于23種設計模式的專題
http://blog.csdn.net/column/details/pattern.html

單例模式

public class Singleton{
    //volatile 關鍵字保證Singleton實例在內存總是最新的
    private volatile static Singleton instance;
    //將默認的構造函數私有化,防止其他類手動new
    private Singleton(){};
    //獲取Singleton實例的唯一方式 
    public static Singleton getInstance(){
        if(instance==null){
            sychronized(Singleton.class){
                if(instance==null)
                    instance=new Singleton();
            }
        }
        return instatnce;
    }
}

單例模式看起來簡單,但其實還有幾個地方需要注意的
1、volatile關鍵字的作用是:線程每次使用到被volatile關鍵字修飾的變量時,都會去堆里拿最新的數據。當一個線程在執行instance=new Singleton()時,其實執行了三個指令

1、給Singleton實例分配內存
2、調用Singleton()構造函數,初始化成員字段
3、將instance對象指向分配的內存(此時instance就不是null啦~)

在jvm執行指令的時候2、3的順序是不確定的,也就是jvm有可能先初始化Singleton實例,然后再把實例只想分配的內存地址,或者先把實例指向內存地址在對實例進行初始化。考慮這樣一種情況:一開始,第一個線程執行instance=new Singleton();這句時,JVM先指向一個堆地址,而此時,又來了一個線程2,它發現instance不是null,就直接拿去用了,但是堆里面對單例對象的初始化并沒有完成,最終出現錯誤。
2、第一次空指針判斷,這個比較好理解,為了防止實例已創建的情況下,避免多線程的不必要的同步阻塞。
3、第二次空指針判斷,主要考慮這樣一種情況:當有2個線程同時到達了sychronized{}處(當并發量很大的時候這種情況是存在的),其中一個線程拿到了 Singleton類鎖(區別對象鎖和類鎖),進入了sychronized{}里面執行完了對象初始化并釋放了類鎖,同時由于第二個線程已經跨過了第一次空指針判斷,所以它會拿到類鎖進入sychronized{},此時的Singleton對象已經被第一個線程初始化了不為空,第二個線程直接跳出同步塊,保證了單例的唯一性。

Builer模式

public class MyBuilder{
    private int id;
    private String num;
    public MyData build(){
        MyData d=new MyData();
        d.setId(id);
        d.setNum(num);
        return t;
    }
    public MyBuilder setId(int id){
        this.id=id;
        return this;
    }
    public MyBuilder setNum(String num){
        this.num=num;
        return this;
    }

}

public class Test{
    public static void  main(String[] args){
        MyData d=new MyBuilder().setId(10).setNum("hc").build();
    }

}

Builer模式比較簡單就不解釋了,當一個對象初始化時所需的參數過多,又不全是必要參數時相當有用,可以參考Android Dialog的創建,那里就使用了builder模式。

原型模式

原型設計模式比較簡單,就是將一個對象進行拷貝,但對象必須要實現Cloneable 接口,像android中的View及其之類就沒有實現Cloneable 不能進行clone。同時要區分原型模式和傳引用的區別。原型模式會創建一個新的對象,而傳引用只是新創建了一個引用但指向的是同一個對象。同時原型模式又分為淺拷貝和深拷貝。
淺拷貝:就是拷貝了【基本數據類型和引用類型的引用】
深拷貝:【引用類型內的所有基本數據類型的值】
參考:https://juejin.im/entry/593dee6c128fe1006aec7c3e

普通工廠模式

public abstract class Product{
    public abstract void method();
} 

public class ConcreteProductA extends Prodect{
    public void method(){
        System.out.println("我是產品A!");
    }
}

public class ConcreteProductB extends Prodect{
    public void method(){
        System.out.println("我是產品B!");
    }
}
public  abstract class Factory{
    public abstract Product createProduct();
}

public class MyFactory extends Factory{

    public Product createProduct(){
        return new ConcreteProductA();
    }
}

其實普通工廠方法就是一種工廠生產一種產品,工廠和產品都繼承對應的抽象類。比較簡單就不多分析了,大家自己看代碼把。

抽象工廠模式

public abstract class AbstractProductA{
    public abstract void method();
}
public abstract class AbstractProdectB{
    public abstract void method();
}

public class ConcreteProductA1 extends AbstractProductA{
    public void method(){
        System.out.println("具體產品A1的方法!");
    }
}
public class ConcreteProductA2 extends AbstractProductA{
    public void method(){
        System.out.println("具體產品A2的方法!");
    }
}
public class ConcreteProductB1 extends AbstractProductB{
    public void method(){
        System.out.println("具體產品B1的方法!");
    }
}
public class ConcreteProductB2 extends AbstractProductB{
    public void method(){
        System.out.println("具體產品B2的方法!");
    }
}

public abstract class AbstractFactory{
    public abstract AbstractProductA createProductA();

    public abstract AbstractProductB createProductB();
}

public  class ConcreteFactory1 extends AbstractFactory{
    public  AbstractProductA createProductA(){
        return new ConcreteProductA1();
    }

    public  AbstractProductB createProductB(){
        return new ConcreteProductB1();
    }
}

public  class ConcreteFactory2 extends AbstractFactory{
    public  AbstractProductA createProductA(){
        return new ConcreteProductA2();
    }

    public  AbstractProductB createProductB(){
        return new ConcreteProductB2();
    }
}

從代碼中可用看到,抽象工廠和普通工廠很相似,唯一不同的是普通工廠每個工廠對應只生產一種產品,而抽象工廠每個工廠會生產一組相關或者相互依賴的產品。

策略模式

策略模式從字面來理解其實就是一種編程策略(這不是廢話嗎,說跟沒說一樣),方便我們只要進行簡單的對象替換,就能實現功能相同,但實現方式不同的算法。幾個例子:我們現在要對數組進行排序,我們有好多排序算法,冒泡,二分法,選擇等等。假如沒有采用策略模式,而是采用用編碼 我們有可能會這樣做(js 方便點)

function executeSort(type){
        if(type === "冒泡"){
          ....................
      }else if(type === "二分法"){
          .....................
      }else if(type === "選擇"){
          .....................
      }
  .......................
}

executeSort("冒泡")
executeSort("二分法")
executeSort("選擇")

貌似也還行,不算分復雜嗎,但是這個的前提我們事先已經定義好了這幾種算法,可是萬一有一天,我們發現現有的算法滿足不了我們的需求,我們要新增一種排序算法,加入就叫"史上最快排序"算法,我們怎么辦,我們要新增

else if(type === "史上最快排序"){
          .....................
      }
executeSort("史上最快排序")

也還好,改的不多,然后產品所我們要新增"史上最快排序1"~"史上最快排序9",我估計你會看著一大串的 else if想要爆粗口吧,最后戀看都不想看一下代碼。好的,讓我們換一種方式來實現:

interface AbstractSort{
   void sort();
}

class 史上最快排序1 implements AbstractSort{
    void sort(){

    }
}
.......
class 史上最快排序9 implements AbstractSort{
    void sort(){

    }
}

void executeSort(AbstractSort sort){
   sort.sort()
}

executeSort(new 史上最快排序1() )

要新增的時候我們只需要新增一個類并修改一行代碼就夠了,每個類實現自己的算法,多么清晰。

狀態模式

狀態模式和策略模式在編碼方式上很像,但是要表達東西和概念卻不一樣。
狀態模式中,行為是有狀態來決定的,不同的狀態下會執行不同的行為。舉個例子把,比如電視,電視有2個狀態,一個是開機,一個是關機,開機時可以切換頻道,關機時切換頻道不做任何響應。
策略模式:每個模式是相互獨立的,可以相互替換,得到的結果是一樣的,只是實現方式不同
狀態模式:平行的不可替換的,因為每種狀態下表現出來的行為是不一樣的。

責任鏈模式

多個對象都有機會處理請求,從而避免請求的發送者和接受者直接的耦合關系,將這些對象連成一條鏈,并沿這條鏈傳遞該請求,直到有對象處理它為止。
幾個例子,Android中的view事件傳遞就用到了責任鏈模式,最外層的Activity首先捕獲到了觸摸事件,如果自己不處理,則將事件傳遞給子view處理,一直到有view處理為止(其實這跟js中的原型鏈有點像,感興趣的朋友可以去了解一下)。這里就不貼代碼了,大家可以去看下Android事件傳遞的源碼。里面涉及到好多設計模式(最主要的是策略+責任鏈)。

解釋器模式

概念:給定一個語言,定義它的語法,并定義一個解釋器,這個解釋器用于解析語言。
舉個例子,比如我們在xml中定義的布局如何轉化成我們常見的對象,這其實就用到了解釋器模式。解釋器模式的側重點在于解析。

適配器模式

概念:把一個類的接口變換成客戶端所期待的另一個接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
舉個最簡單的例子: 比較典型的有ListView和RecyclerView,其實適配器模式就是把2中不相關的對象連接在一起了,因為listview其實只關心itemview,不關心data,那怎樣將數據展現在listview里呢,這里就用到了adapter。其實適配器模式的作用就是適配和連接。我們生活中的電源線插頭其實就是一種適配器模式。插頭將我們的各種電器和插口連接起來,每種電器都有可能有不同的接口,但是電源線將這種連接給屏蔽掉了。仔細觀察,生活當中處處都是知識點啊。

命令模式

舉個生活中的日子:我點擊電腦的關機鍵,我們就會得到一個關機的最終結果,那至于在電腦處于關機狀態之前發生了什么(保存數據,結束進程,切斷電源等等),不需要我們關心。
所以簡單來講就是將一系列相關的動作封裝在一起,提供給調用者一個統一的接口,通過調用不同的參數,最終展現出基于參數的唯一結果(感覺有點類似函數式編程中的純函數概念)。

觀察者模式

觀察者模式我們平時用得比較多,存在1對n的概念,當1發生變化時,所有的n都會接收到通知,比如rxjava就是充分展現了觀察者模式的精髓,大家有時間可以去看下相關源碼,里面有三個主要的角色:觀察者、被觀察者、訂閱,當訂閱關系發生時,觀察者就能接收到被觀察所觸發的行為。

備忘錄模式(參考網上的定義)

備忘錄模式定義:在不破壞封閉的前提下,捕獲一個對象的內部狀態,并在對象之外保存這個狀態,這樣,以后就可將對象恢復到原先保存的狀態中。

其實就是相當于一個提前備份,一旦出現啥意外,能夠恢復。像我們平時用的word軟件,意外關閉了,它能幫我們恢復。其實就是它自動幫我們備份過。

那么Android哪里用到了備忘錄模式呢?Activity的onSaveInstanceState和onRestoreInstanceState就是用到了備忘錄模式,分別用于保存和恢復

迭代器模式(參考網上的定義)

迭代器模式定義:提供一種方法順序訪問一個容器對象中的各個元素,而不需要暴露該對象的內部表示。

相信熟悉Java的你肯定知道,Java中就有迭代器Iterator類,本質上說,它就是用迭代器模式。

按照慣例,看看Android中哪里用到了迭代器模式,Android源碼中,最典型的就是Cursor用到了迭代器模式,當我們使用SQLiteDatabase的query方法時,返回的就是Cursor對象,通過如下方式去遍歷:

cursor.moveToFirst();
do{
//cursor.getXXX(int);
}while(cursor.moveToNext);

模板方法模式

模板方法其實在工作中用到得比較多,一般用于業務流程中,我們在抽象中定義執行路口,提供給外部調用,然后把具體的實現方法通過繼承的方式讓之類去實現。他們根本不需要去關心整體的業務流程,只需要去實現自己的具體方法就行了。

直接拿Android中的源碼來說事!我們知道,啟動一個Activity過程非常復雜,如果讓開發者每次自己去調用啟動Activity過程無疑是一場噩夢。好在啟動Activity大部分代碼時不同的,但是有很多地方需要開發者定制。也就是說,整體算法框架是相同的,但是將一些步驟延遲到子類中,比如Activity的onCreate、onStart等等。這樣子類不用改變整體啟動Activity過程即可重定義某些具體的操作了~。

訪問者模式

這種模式好像在實際開發中運用的并不多,以下文字網上博文。
定義:封裝一些作用于某種數據結構中各元素的操作,它可以在不改變這個數據結構的前提下定義作用于這些元素的新的操作。

訪問者模式是23種設計模式中最復雜的一個,但他的使用率并不高,大部分情況下,我們不需要使用訪問者模式,少數特定的場景才需要。

Android中運用訪問者模式,其實主要是在編譯期注解中,編譯期注解核心原理依賴APT(Annotation Processing Tools),著名的開源庫比如ButterKnife、Dagger、Retrofit都是基于APT。

中介者模式

中介者模式感覺就像一個大管家,在某一項具體的業務中,全部都可以通過他來完成,而不需要對接這個模塊中的其他對象。看看生活中的房產中介應該就比較好理解了,他連接了買方和賣方,而這2種角色卻可以不用直接對接。用另一句話來說就是專業的人做專業的事,當然不同的對象就要負責自己擅長的事。
我們知道系統啟動時,各種系統服務會向ServiceManager提交注冊,即ServiceManager持有各種系統服務的引用 ,當我們需要獲取系統的Service時,比如ActivityManager、WindowManager等(它們都是Binder),首先是向ServiceManager查詢指定標示符對應的Binder,再由ServiceManager返回Binder的引用。并且客戶端和服務端之間的通信是通過Binder驅動來實現,這里的ServiceManager和Binder驅動就是中介者

代理模式&裝飾模式

裝飾器模式關注于在一個對象上動態的添加方法,然而代理模式關注于控制對對象的訪問。換句話 說,用代理模式,代理類(proxy class)可以對它的客戶隱藏一個對象的具體信息。可能還算比較模糊,用網上的2中uml圖來理解一下。
代理模式:


ProxyPattern.png

裝飾者模式


DecoratorPattern.png

從圖中我們可以看到2總模式非常類似,唯一不同的是在代理模式中,代理類似直接持有被代理對象的,而在調用時也是直接創建的代理類,初始化代理類的同時也初始化了被代理類。而裝飾著模式中,裝飾類和被裝飾類往往不存在直接的引用,裝飾類持有雙方共同的抽象類,在調用的時候通過構造函數傳入。
總結

1、從語意上講,代理模式是為控制對被代理對象的訪問,而裝飾模式是為了增加被裝飾對象的功能
2、代理類所能代理的類完全由代理類確定,裝飾類裝飾的對象需要根據實際使用時客戶端的組合來確定
3、被代理對象由代理對象創建,客戶端甚至不需要知道被代理類的存在;被裝飾對象由客戶端創建并傳給裝飾對象

組合模式

例子:對于一家大型公司,每當公司高層有重要事項需要通知到總部每個部門以及分公司的各個部門時,并不希望逐一通知,而只希望通過總部各部門及分公司,再由分公司通知其所有部門。這樣,對于總公司而言,不需要關心通知的是總部的部門還是分公司
很簡單 直接上uml圖


image.png

享元模式(來自于網絡)

定義:使用享元對象有效地支持大量的細粒度對象。

享元模式我們平時接觸真的很多,比如Java中的常量池,線程池等。主要是為了重用對象。

在Android哪里用到了享元模式呢?線程通信中的Message,每次我們獲取Message時調用Message.obtain()其實就是從消息池中取出可重復使用的消息,避免產生大量的Message對象。

外觀模式

定義:要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行。

怎么理解呢,舉個例子,我們在啟動計算機時,只需按一下開關鍵,無需關系里面的磁盤、內存、cpu、電源等等這些如何工作,我們只關心他們幫我啟動好了就行。實際上,由于里面的線路太復雜,我們也沒辦法去具體了解內部電路如何工作。主機提供唯一一個接口“開關鍵”給用戶就好。

那么Android哪里使用到了外觀模式呢?依然回到Context,Android內部有很多復雜的功能比如startActivty、sendBroadcast、bindService等等,這些功能內部的實現非常復雜,如果你看了源碼你就能感受得到,但是我們無需關心它內部實現了什么,我們只關心它幫我們啟動Activity,幫我們發送了一條廣播,綁定了Activity等等就夠了。

橋接模式

橋接模式(Bridge Pattern),將抽象部分與它的實現部分分離,使它們都可以獨立地變化。更容易理解的表述是:實現系統可從多種維度分類,橋接模式將各維度抽象出來,各維度獨立變化,之后可通過聚合,將各維度組合起來,減少了各維度間的耦合。

對比一下一下2張圖

不必要的繼承導致類爆炸

image.png

從上圖可以看到,對于每種組合都需要創建一個具體類,如果有N個維度,每個維度有M種變化,則需要MNMN個具體類,類非常多,并且非常多的重復功能。

如果某一維度,如Transmission多一種可能,比如手自一體檔(AMT),則需要增加3個類,BMWAMT,BenZAMT,LandRoverAMT。

橋接模式類圖

image.png

從上圖可知,當把每個維度拆分開來,只需要M*N個類,并且由于每個維度獨立變化,基本不會出現重復代碼。
此時如果增加手自一體檔,只需要增加一個AMT類即可

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容