Android 設(shè)計(jì)模式之面向?qū)ο蟮牧笤瓌t

在日常開(kāi)發(fā)過(guò)程中時(shí)常需要用到設(shè)計(jì)模式,但是設(shè)計(jì)模式有23種述么,如何將這些設(shè)計(jì)模式了然于胸并且能在實(shí)際開(kāi)發(fā)過(guò)程中應(yīng)用得得心應(yīng)手呢?和我一起跟著《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》一書邊學(xué)邊應(yīng)用吧盗飒!


今天我們要講的是面向?qū)ο蟮牧笤瓌t

單一職責(zé)原則

就一個(gè)類而言置鼻,應(yīng)該僅有一個(gè)引起它變化的原因。簡(jiǎn)單來(lái)說(shuō)取逾,一個(gè)類中應(yīng)該是一組相關(guān)性很高的函數(shù)耗绿、數(shù)據(jù)的封裝。
  • 我們?cè)贏pp中往往會(huì)用到很多的公共方法砾隅,比如獲取系統(tǒng)的時(shí)間误阻,這個(gè)功能可能在App中的很多地方都要用到。這個(gè)時(shí)候我們一般會(huì)單獨(dú)寫個(gè)工具類TimeUtils晴埂,把處理時(shí)間有關(guān)的方法都放到這個(gè)類里面究反,這樣就能減少重復(fù)代碼,App的結(jié)構(gòu)會(huì)更加清晰儒洛。當(dāng)需要添加其他跟時(shí)間有關(guān)的方法時(shí)精耐,就可以都加到這個(gè)TimeUtils類里面,這就是我們平時(shí)遵循的單一職責(zé)原則琅锻。

開(kāi)閉原則

軟件中的對(duì)象(類黍氮、模塊唐含、函數(shù)等),應(yīng)該對(duì)于擴(kuò)展是開(kāi)放的沫浆,而對(duì)于修改是封閉的捷枯。
  • 我們?cè)谲浖_(kāi)發(fā)過(guò)程中就要考慮到后續(xù)的擴(kuò)展和修改。比如說(shuō)专执,我們?cè)陂_(kāi)發(fā)一款類似于universal-image-loader的圖片加載框架淮捆,可能一開(kāi)始我們的功能比較簡(jiǎn)單,圖片緩存只有內(nèi)存緩存本股。當(dāng)我們新版本需要添加SD卡緩存時(shí)攀痊,就要注意盡可能的減少對(duì)原來(lái)代碼的修改,因?yàn)檫@樣很可能會(huì)引入新的bug拄显。而要做到開(kāi)閉原則苟径,一般有2種途徑,一是通過(guò)繼承原有的類躬审;二是通過(guò)抽象和接口棘街。后面我們會(huì)拿書中的圖片加載框架來(lái)具體說(shuō)明。

里氏替換原則

所有引用基類的地方必須能透明的使用其子類承边。通俗的說(shuō)遭殉,就是只要父類能出現(xiàn)的地方子類就可以出現(xiàn),而且替換為子類以后不會(huì)出現(xiàn)任何錯(cuò)誤或異常博助。反過(guò)來(lái)就不行了险污,子類出現(xiàn)的地方父類不一定能適應(yīng)。
  • 要實(shí)現(xiàn)里氏替換原則富岳,一般需要一個(gè)抽象的父類蛔糯,父類中定義了子類的公共方法,子類繼承或是實(shí)現(xiàn)父類以后擴(kuò)展不同的功能窖式,這樣以來(lái)可以實(shí)現(xiàn)根據(jù)不同的需要來(lái)應(yīng)用對(duì)應(yīng)的子類蚁飒,從而達(dá)到應(yīng)用不同的功能的目的,程序的擴(kuò)展性大大增強(qiáng)脖镀。同時(shí)這也體現(xiàn)了開(kāi)閉原則,即當(dāng)需要增加新功能時(shí)狼电,只要繼承或?qū)崿F(xiàn)父類蜒灰,實(shí)現(xiàn)新增的功能就達(dá)到了擴(kuò)展的目的,而不是直接修改原來(lái)的代碼肩碟,也即對(duì)擴(kuò)展開(kāi)放强窖,對(duì)修改封閉。

依賴倒置原則

依賴倒置原則在Java中的表現(xiàn)就是:模塊間的依賴通過(guò)抽象發(fā)生削祈,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系翅溺,其依賴關(guān)系是通過(guò)接口或抽象類產(chǎn)生的脑漫。
  • 一句話就是依賴抽象而不依賴具體的實(shí)現(xiàn)。比如上面我們說(shuō)開(kāi)發(fā)一個(gè)圖片加載框架咙崎,那我們肯定會(huì)根據(jù)單一職責(zé)原則劃分不同的模塊优幸,比如網(wǎng)絡(luò)加載模塊,圖片緩存模塊褪猛,以及主模塊等网杆。主模塊肯定會(huì)調(diào)用圖片緩存模塊,如果我們調(diào)用的是圖片緩存模塊的具體實(shí)現(xiàn)伊滋,那么當(dāng)我們修改圖片模塊時(shí)就很可能要對(duì)應(yīng)修改主模塊碳却,這就是耦合了。一個(gè)比較好的做法是將圖片緩存模塊抽象出來(lái)笑旺,而主模塊調(diào)用這個(gè)抽象即可昼浦,這樣也就是依賴抽象了。

接口隔離原則

類間的依賴關(guān)系應(yīng)該建立在最小的接口上筒主。
  • 接口隔離原則就是讓客戶端依賴的接口盡可能的小关噪。就是在上面提到的依賴倒置(依賴抽象而不是實(shí)現(xiàn))原則的基礎(chǔ)上,增加一個(gè)最小化依賴的原則物舒。說(shuō)白了就是在依賴接口的基礎(chǔ)上依賴盡可能少的接口色洞。
  • 這里舉個(gè)例子:
<!--將圖片寫入SD卡-->
public void put(String url, Bitmap bitmap) {
    FileOutputStream fileOutputStream = null;
    try {
        fileOutputStream = new FileOutputStream(SDPath+url);
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
    }catch (FileNotFoundException e) {
        e.printStackTrace();
    }finally {
        CloseUtils.closeQuietly(fileOutputStream);
    }
}

<!--關(guān)閉工具類-->
public final class CloseUtils {

    private CloseUtils() { }

    /**
     * 關(guān)閉Closeable對(duì)象
     * @param closeable
     */
    public static void closeQuietly(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 上述例子中的CloseUtils的closeQuietly方法的原理就是依賴于Closeable抽象而不是具體實(shí)現(xiàn)(依賴倒置原則),并且建立在最小化依賴的基礎(chǔ)上冠胯,它只要知道這個(gè)對(duì)象是可以關(guān)閉的就行了火诸,其他的一概不用關(guān)心,這就是接口隔離原則

迪米特原則

一個(gè)對(duì)象應(yīng)該對(duì)其他的對(duì)象有最少的了解
  • 通俗的講荠察,一個(gè)類應(yīng)該對(duì)自己需要耦合或調(diào)用的類知道得最少置蜀,調(diào)用者或是依賴者只要知道它需要的方法即可。要做到這個(gè)原則悉盆,需要我們對(duì)各個(gè)模塊之間的功能進(jìn)行很好的區(qū)分和分配盯荤,把相互之間的依賴和耦合減到最少。

以上就是面向?qū)ο蟮牧笤瓌t焕盟。

下面我們通過(guò)書中的圖片加載框架ImageLoader的例子介紹這些原則的具體應(yīng)用秋秤。
  • 首先是ImageLoader類
public class ImageLoader {
    // 圖片緩存,依賴接口脚翘,而不是具體實(shí)現(xiàn)
    // 如果改為MemoryCache mImageCache = new MemoryCache();就不能定制圖片緩存的實(shí)現(xiàn)灼卢,擴(kuò)展性大大降低,耦合度也會(huì)大大提高
    ImageCache mImageCache = new MemoryCache();
    // 線程池来农,線程數(shù)量為CPU的數(shù)量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    // 注入緩存,對(duì)擴(kuò)展開(kāi)放鞋真,對(duì)修改關(guān)閉
    public void setImageCache(ImageCache cache) {
        mImageCache = cache;
    }

    /**
     * 顯示圖片
     * @param imageUrl
     * @param imageView
     */
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        // 圖片沒(méi)有緩存,提交到線程池下載
        submitLoadRequest(imageUrl, imageView);
    }

    /**
     * 下載圖片
     * @param imageUrl
     * @param imageView
     */
    private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
        imageView.setTag(imageUrl);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(imageUrl);
                if (bitmap == null) {
                    return;
                }
                if (imageUrl.equals(imageView.getTag())) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(imageUrl, bitmap);
            }
        });
    }

    /**
     * 下載圖片
     * @param imageUrl
     * @return
     */
    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
  • 然后是圖片緩存接口以及內(nèi)存緩存
<!--圖片緩存接口-->
public interface ImageCache {
    public Bitmap get(String url);
    public void put(String url, Bitmap bitmap);
}

<!--內(nèi)存緩存的實(shí)現(xiàn)-->
public class MemoryCache implements ImageCache{
    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCache() {
        //初始化LRU緩存
        initImageCache();
    }
    
    private void initImageCache() {
        // 計(jì)算可使用的最大內(nèi)存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024);
        // 取四分之一的可用內(nèi)存作為緩存
        final int cacheSize = maxMemory / 4;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }


    @Override
    public Bitmap get(String url) {
        return mMemoryCache.get(url);
    }

    @Override
    public void put(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
    }
}
ImageLoader類中的邏輯比較直觀沃于,六大原則的體現(xiàn)主要在圖片緩存的處理上涩咖。
  • 將圖片緩存單獨(dú)出去而不是寫在ImageLoader一起海诲,體現(xiàn)了單一職責(zé)原則
  • ImageLoader中依賴的是圖片緩存的接口而不是具體的實(shí)現(xiàn),體現(xiàn)了開(kāi)閉原則檩互、里氏替換原則和依賴倒置原則特幔。
  • ImageLoader類和MemoryCache類之間只依賴ImageCache接口,也可以說(shuō)體現(xiàn)了接口隔離原則
  • ImageLoader類只需要知道MemoryCache類的put方法和get方法盾似,其他的實(shí)現(xiàn)一概不管敬辣,也體現(xiàn)了迪米特原則
當(dāng)然以上的ImageLoader還只是初版,還有很多有待優(yōu)化的地方零院,比如可以把下載的邏輯單獨(dú)出去溉跃,可以增加更多的定制功能等。

六大原則能讓我們的代碼結(jié)構(gòu)更加合理告抄,擴(kuò)展性更好撰茎,讓程序更穩(wěn)定靈活。這些原則往往都是共同作用的打洼,比如上面例子中的CloseUtils滿足了單一職責(zé)原則龄糊,closeQuietly方法運(yùn)用了依賴倒置,并且遵循接口隔離原則募疮。趕緊用六大原則優(yōu)化你的代碼吧l懦汀!阿浓!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末他嚷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芭毙,更是在濱河造成了極大的恐慌筋蓖,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件退敦,死亡現(xiàn)場(chǎng)離奇詭異粘咖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)侈百,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門瓮下,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人钝域,你說(shuō)我怎么就攤上這事讽坏。” “怎么了网梢?”我有些...
    開(kāi)封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵震缭,是天一觀的道長(zhǎng)赂毯。 經(jīng)常有香客問(wèn)我战虏,道長(zhǎng)拣宰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任烦感,我火速辦了婚禮巡社,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘手趣。我一直安慰自己晌该,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布绿渣。 她就那樣靜靜地躺著朝群,像睡著了一般。 火紅的嫁衣襯著肌膚如雪中符。 梳的紋絲不亂的頭發(fā)上姜胖,一...
    開(kāi)封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音淀散,去河邊找鬼右莱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛档插,可吹牛的內(nèi)容都是我干的慢蜓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼郭膛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晨抡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起饲鄙,我...
    開(kāi)封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凄诞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后忍级,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體帆谍,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年轴咱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汛蝙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朴肺,死狀恐怖窖剑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戈稿,我是刑警寧澤西土,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站鞍盗,受9級(jí)特大地震影響需了,放射性物質(zhì)發(fā)生泄漏跳昼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一肋乍、第九天 我趴在偏房一處隱蔽的房頂上張望鹅颊。 院中可真熱鬧,春花似錦墓造、人聲如沸堪伍。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)帝雇。三九已至,卻和暖如春蛉拙,著一層夾襖步出監(jiān)牢的瞬間摊求,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工刘离, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留室叉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓硫惕,卻偏偏與公主長(zhǎng)得像茧痕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恼除,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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