理解面對對象的六大原則

以下內(nèi)容來自:《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》

說起面對對象的六大原則茅郎,可能大部分人都能說出一二來蜗元。但是如何應(yīng)用到自己的代碼中卻是一個不小的難題。這篇文章會用一個實(shí)際的例子系冗,并用六大原則改造奕扣,在改造的過程中體會。

我們來看一個Android中常見的功能模塊——圖片加載掌敬。在不使用任何現(xiàn)有框架的前提下惯豆,可能會這樣寫:

public class ImageLoader {
    //圖片緩存
    LruCache<String, Bitmap> mImageCache;
    //固定線程數(shù)的線程池,線程數(shù)為CPU的數(shù)量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public ImageLoader() {
        initImageCache();
    }

    private void initImageCache() {
        //計(jì)算可使用的最大內(nèi)存
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //使用1/4作為圖片緩存
        int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() * 1024;
            }
        };
    }

    public void displayImage(final String url, final ImageView imageView) {
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                if (imageView.getTag().equals(url)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, bitmap);
            }
        });
    }

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

這樣可以完成圖片加載的功能奔害,但是這個ImageLoader嚴(yán)重耦合楷兽,毫無設(shè)計(jì)可言,更不用說擴(kuò)展性华临、靈活性了芯杀。所有的功能都寫在一個類里,隨著功能的增加,ImageLoader類會越來越大揭厚,代碼也越來越復(fù)雜却特,圖片加載系統(tǒng)就越來越脆弱。接下來我們嘗試用單一職責(zé)原則改造以下這個ImageLoader棋弥。

單一職責(zé)原則

**Single Responsibility Principle **

定義:就一個類而言核偿,應(yīng)該僅有一個引起它變化的原因。簡單來說顽染,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)漾岳、數(shù)據(jù)的封裝。

雖然如何劃分一個類粉寞,一個函數(shù)的職責(zé)尼荆,每個人都有自己的看法,這需要根據(jù)個人經(jīng)驗(yàn)唧垦、具體的業(yè)務(wù)邏輯而定捅儒。但完全兩個不一樣的功能就不應(yīng)該放在一個類中。因此從單一職責(zé)來看ImageLoader振亮,明顯它可以分為兩個巧还,一個是圖片加載;另一個是圖片緩存坊秸。因此我們這樣改造:

public class ImageLoader {
    //圖片緩存
    ImageCache mImageCache = new ImageCache();
    //固定線程數(shù)的線程池麸祷,線程數(shù)為CPU的數(shù)量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                if (imageView.getTag().equals(url)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, bitmap);
            }
        });
    }

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

抽出ImageCache用于處理圖片緩存:

public class ImageCache {
    LruCache<String, Bitmap> mImageCache;

    public ImageCache() {
        initImageCache();
    }

    private void initImageCache() {
        //計(jì)算可使用的最大內(nèi)存
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //使用1/4作為圖片緩存
        int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() * 1024;
            }
        };
    }

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
    }

    public Bitmap get(String url) {
        return mImageCache.get(url);
    }
}

改造后的ImageLoader只負(fù)責(zé)圖片加載的邏輯,而ImageCache只負(fù)責(zé)處理圖片緩存的邏輯褒搔,這樣ImageLoader的代碼量變少了阶牍,職責(zé)也清晰了;當(dāng)緩存相關(guān)的邏輯需要改變時星瘾,不需要修改ImageLoader類走孽,而圖片的加載邏輯需要修改時也不會影響到緩存處理邏輯。

開閉原則

Open Close Principe

定義:軟件中的對象(類琳状、模塊磕瓷、函數(shù)等)應(yīng)該對于擴(kuò)展是開放的,但是對于修改是封閉的念逞。在軟件的生命周期內(nèi)困食,因?yàn)樽兓⑸壓途S護(hù)等原因需要對軟件原有代碼進(jìn)行修改時肮柜,可能會將錯誤引入原本已經(jīng)經(jīng)過測試的舊代碼中,破壞原有系統(tǒng)倒彰。因此审洞,當(dāng)軟件需要變化時,我們應(yīng)該盡量通過擴(kuò)展的方式來實(shí)現(xiàn)變化么人不是通過修改已有的代碼來實(shí)現(xiàn)。當(dāng)然芒澜,在現(xiàn)實(shí)開發(fā)中仰剿,只通過繼承的方式來升級、維護(hù)原有系統(tǒng)只是一個理想化的愿景痴晦,因此南吮,在實(shí)際的開發(fā)過程中,修改原有代碼誊酌、擴(kuò)展代碼往往是同時存在的部凑。

我們再來看看ImageLoader,雖然通過內(nèi)存緩存解決了每次從網(wǎng)絡(luò)加載圖片的問題碧浊,但是涂邀,Android應(yīng)用的內(nèi)存很有限,且具有易失性箱锐,即當(dāng)應(yīng)用重新啟動之后比勉,原來已經(jīng)加載過的圖片將會失去,這樣重啟之后就需要重新下載驹止!這又會導(dǎo)致加載緩慢浩聋、耗費(fèi)用戶流量的問題。引入SD卡緩存可以解決這一個問題臊恋,我們在來添加一個SD卡緩存類:

public class DiskCache {
    static String cacheDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";

    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }

    public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

然后相應(yīng)的衣洁,改動ImageLoader的代碼,添加SD卡緩存:

public class ImageLoader {
    //內(nèi)存緩存
    ImageCache mImageCache = new ImageCache();
    //SD卡緩存
    DiskCache mDiskCache = new DiskCache();
    //是否使用SD卡緩存
    boolean isUseDiskCache = false;
    //固定線程數(shù)的線程池捞镰,線程數(shù)為CPU的數(shù)量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void displayImage(final String url, final ImageView imageView) {
        //判斷使用哪種緩存
        Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                if (imageView.getTag().equals(url)) {
                    imageView.setImageBitmap(bitmap);
                }
                if (isUseDiskCache) {
                    mDiskCache.put(url, bitmap);
                } else {
                    mImageCache.put(url, bitmap);
                }
            }
        });
    }

    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache;
    }

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

這里增加了一個DiskCache類闸与,用戶可以通過useDiskCache方法來使用哪種緩存進(jìn)行設(shè)置,例如:

ImageLoader imageLoader = new ImageLoader();
//使用SD卡緩存
imageLoader.useDiskCache(true);
//使用內(nèi)存緩存
imageLoader.useDiskCache(false);

但是這樣又存在不能同時使用內(nèi)存緩存和SD卡緩存岸售。應(yīng)該優(yōu)先使用內(nèi)存緩存践樱,如果內(nèi)存緩存中沒有圖片再使用SD卡緩存,如果SD卡中也沒有圖片最后才去網(wǎng)絡(luò)上獲取凸丸,這才是最好的緩存策略拷邢。我們在添加一種雙緩存的類DoubleCache:

public class DoubleCache {
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();

    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    public void put(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
        mDiskCache.put(url, bitmap);
    }
}

并且同樣應(yīng)用到ImageLoader中:

public class ImageLoader {
    //內(nèi)存緩存
    ImageCache mImageCache = new ImageCache();
    //SD卡緩存
    DiskCache mDiskCache = new DiskCache();
    //雙緩存
    DoubleCache mDoubleCache = new DoubleCache();
    //使用SD卡緩存
    boolean isUseDiskCache = false;
    //使用雙緩存
    boolean isUseDoubleCache = false;
    //固定線程數(shù)的線程池,線程數(shù)為CPU的數(shù)量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void displayImage(final String url, final ImageView imageView) {
        //判斷使用哪種緩存
        Bitmap bitmap = null;
        if (isUseDoubleCache) {
            bitmap = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bitmap = mDiskCache.get(url);
        } else {
            bitmap = mImageCache.get(url);
        }
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        //沒有緩存屎慢,則提交給線程池進(jìn)行異步下載
    }

    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache;
    }

    public void useDoubleCache(boolean useDoubleCache) {
        isUseDoubleCache = useDoubleCache;
    }
}

現(xiàn)在即可以使用內(nèi)存緩存瞭稼,也可以使用SD卡緩存,或者兩者同時使用腻惠,但還是存在一些問題环肘。我們來分析一下現(xiàn)在的代碼,ImageLoader通過boolean變量來讓用戶選擇使用哪種緩存集灌,因此存在各種if-else判斷語句悔雹,通過這些判斷來確定使用哪種緩存。隨著這些邏輯的引入,代碼變得越來越復(fù)雜腌零、脆弱梯找,如果一不小心寫錯了某個if條件,那就需要更多的時間來排除益涧,整個ImageLoader類也會變得越來越臃腫锈锤。還有一點(diǎn),用戶不能自己實(shí)現(xiàn)緩存注入到ImageLoader中闲询,可擴(kuò)展性差久免。

這樣的代碼明顯不滿足開閉原則,為了滿足上面說到的需求嘹裂,我們可以采用策略模式繼續(xù)改造妄壶,先來看下UML圖:

UML圖

接著,我們把代碼按照UML圖改造一下,ImageCache變成了接口寄狼,并且有3個實(shí)現(xiàn)類:MemoryCache丁寄、DiskCache、DoubleCache泊愧。

public interface ImageCache {
    void put(String url, Bitmap bitmap);

    Bitmap get(String url);
}

public class MemoryCache implements ImageCache {
    ...
}

public class DiskCache implements ImageCache {
    ...
}

public class DoubleCache implements ImageCache {
    ...
}

而ImageLoader變成這樣:

public class ImageLoader {
    //緩存
    ImageCache mImageCache = new MemoryCache();
    //固定線程數(shù)的線程池伊磺,線程數(shù)為CPU的數(shù)量
    ExecutorService mExecutorService =
            Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void setImageCache(ImageCache imageCache) {
        mImageCache = imageCache;
    }

    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        //圖片沒有緩存,提交到線程池中下載
        submitLoadRequest(url, imageView);
    }

    private void submitLoadRequest(final String url, final ImageView imageView) {
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = downloadImage(url);
                if (bitmap == null) {
                    return;
                }
                if (imageView.getTag().equals(url)) {
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url, bitmap);
            }
        });
    }

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(connection.getInputStream());
            connection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

這里的增加了一個setImageCache(ImageCache imageCache) 函數(shù)删咱,用戶可以通過該函數(shù)設(shè)置緩存實(shí)現(xiàn)屑埋,也就是通常說的依賴注入:

//默認(rèn)使用內(nèi)存緩存
ImageLoader imageLoader = new ImageLoader();
//使用SD卡緩存
imageLoader.setImageCache(new DiskCache());
//使用雙緩存
imageLoader.setImageCache(new DoubleCache());
//使用自定義緩存
imageLoader.setImageCache(new ImageCache(){

    @Ovrride
    public void put(String url, Bitmap bitmap){
        //緩存圖片
    }

    @Ovrride
    public Bitmap get(String url){
        retrun /*從緩存獲取圖片*/;
    }
});

現(xiàn)在我們再來看看,無論之后新增加什么緩存痰滋,都可以實(shí)現(xiàn)ImageCache接口摘能,然后通過setImageCache方法注入,在這個過程中我們都不用修改ImageLoader類的代碼敲街,這就是開閉原則:對擴(kuò)展是開放的团搞,對修改是封閉的。而設(shè)計(jì)模式是前人總結(jié)的能更好地遵守原則的方式多艇。

里氏替換原則

Liskov Substitution Principle

定義:如果對每一個類型為S的對象O1逻恐,都有類型為T的獨(dú)享O2,使得以T定義的所有程序P在所有的對象O1都代替成O2時峻黍,程序P的行為沒有發(fā)生變化复隆,那么類型S是類型T的子類型。

看定義的話不是很好理解姆涩,換一種描述方式就是:所有引用基類的地方必須能透明地使用其子類的對象挽拂。

我們來看看Android中Window與View的關(guān)系:

Window與View的關(guān)系

Window依賴于View,而View定義了一個視圖抽象骨饿,measure是各個子類共享的方法亏栈,子類通過復(fù)寫View的draw方法實(shí)現(xiàn)具有各自特色的功能洪鸭,即繪制自身內(nèi)容。任何繼承自View類的子類都可以設(shè)置給show方法仑扑,就是所說的里式替換。通過里式替換置鼻,就可以自定義各式各樣镇饮、千變?nèi)f化的View怜校,然后傳遞給Window屁桑,Window負(fù)責(zé)組織View酬滤,并且將View顯示到屏幕上格嗅。

里式替換的核心原理是抽象粟耻,抽象又依賴于繼承這個特性释移,在OOP當(dāng)中祖秒,繼承的優(yōu)缺點(diǎn)都相當(dāng)明顯:優(yōu)點(diǎn)有以下幾點(diǎn):

  1. 代碼重用究反,減少創(chuàng)建類的成本聂喇,每個子類都擁有父類的方法和屬性辖源;
  2. 子類與父類基本相似,但又與父類有所區(qū)別希太;
  3. 提高代碼的可擴(kuò)展性克饶。

繼承的缺點(diǎn):

  1. 繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法誊辉;
  2. 可能造成子類代碼冗余矾湃、靈活性降低,因?yàn)樽宇惐仨殦碛懈割惖膶傩院头椒ā?/li>

事物總是具有兩面性堕澄,如何權(quán)衡利弊都是需要根據(jù)具體情況來做出選擇并加以處理邀跃。

前面ImageLoader的例子也體現(xiàn)了里式替換原則,即MemoryCache蛙紫、DiskCache拍屑、DoubleCache都可以替換ImageCache的工作,并且能夠保證行為的正確性惊来。ImageCache建立獲取緩存圖片丽涩、保存緩存圖片的接口規(guī)范,MemoryCache等根據(jù)接口規(guī)范實(shí)現(xiàn)了相應(yīng)的功能裁蚁,用戶只需要在使用時指定具體的緩存對象就可以動態(tài)地替換ImageLoader中的緩存策略矢渊。這就使得ImageLoader的緩存系統(tǒng)具有無限的可能性,也就保證了可擴(kuò)展性枉证。

開閉原則和里式替換原則往往是生死相依矮男,不離不棄的,通過里式替換來達(dá)到對擴(kuò)展開放室谚,對修改關(guān)閉的效果毡鉴。然而崔泵,這兩個原則都同時強(qiáng)調(diào)了一個OOP的重要特性——抽象,因此猪瞬,在開發(fā)過程中運(yùn)用抽象是走向代碼優(yōu)化的重要一步憎瘸。

依賴倒置原則

Dependence Inversion Principe

定義:是一種特殊的解耦形式,使得高層次的模塊不依賴于低層次的模塊的實(shí)現(xiàn)細(xì)節(jié)的目的陈瘦,依賴模塊被顛倒了幌甘。
這到底是什么意思呢?
依賴倒置原則有以下幾個關(guān)鍵點(diǎn):

  • 高層模塊不應(yīng)該依賴低層模塊痊项,兩者都應(yīng)該依賴其抽象锅风;
  • 抽象不應(yīng)該依賴細(xì)節(jié);
  • 細(xì)節(jié)應(yīng)該依賴抽象鞍泉。

在Java中皱埠,抽象就是指接口或抽象類,兩者都是不能直接實(shí)例化的咖驮;細(xì)節(jié)就是實(shí)現(xiàn)類边器,實(shí)現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié),其特點(diǎn)就是托修,可以直接被實(shí)例化饰抒。高層模塊就是調(diào)用端,低層模塊就是具體實(shí)現(xiàn)類诀黍。依賴倒置原則在Java語言中的表現(xiàn)就是:模塊間的依賴通過抽象發(fā)生袋坑,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的眯勾。通俗點(diǎn)說就是面對接口(抽象)編程枣宫。

如果類與類直接依賴于細(xì)節(jié),那么他們之間就有直接的耦合吃环,當(dāng)具體實(shí)現(xiàn)需要變化時也颤,意味著要同時修改該依賴者的代碼,這限制了系統(tǒng)的可擴(kuò)展性郁轻。ImageLoader的例子一開始直接依賴MemoryCache翅娶,是一個具體實(shí)現(xiàn),而不是一個抽象類或者接口好唯。這導(dǎo)致了我們后面修改其他緩存實(shí)現(xiàn)就需要修改ImageLoader類的代碼竭沫。

最后版本的ImageLoader就很好的體現(xiàn)了依賴抽象,我們抽出的ImageCache的接口骑篙,并且定義了兩個方法蜕提。而ImageLoader依賴的是抽象(ImageCache接口),而不是具體的某個實(shí)現(xiàn)類靶端。當(dāng)需求發(fā)生變化時谎势,我們可以使用其他的實(shí)現(xiàn)替換原有的實(shí)現(xiàn)凛膏。

接口隔離原則

InterfaceSegregation Principe

定義:客戶端不應(yīng)該依賴它不需要的接口。另一種定義:類間的依賴關(guān)系應(yīng)該建議在最小的接口上脏榆。接口隔離原則將非常龐大猖毫、臃腫的接口拆分成更小的和更具體的接口,這樣客戶將會只需要知道他們感興趣的方法须喂。接口隔離原則的目的是系統(tǒng)解開耦合鄙麦,從而容易重構(gòu)、更改和重新部署镊折。

接口隔離原則說白了就是讓客戶端依賴的接口盡可能的小,這樣說可能還是有點(diǎn)抽象介衔,我們還是以一個例子來說明:

public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

這段代碼的可讀性非常差恨胚,各種try-catch嵌套都是些簡單的代碼,但是會嚴(yán)重影響代碼的可讀性炎咖,并且多層級的大括號很容易將代碼寫到錯誤的層級中赃泡。我們來看看如何解決這類問題。
我們可能知道Java中有一個Closeable接口乘盼,該接口標(biāo)識了一個可關(guān)閉的對象升熊,它只有一個close方法。FileOutputStream就實(shí)現(xiàn)這個接口绸栅。我們可以嘗試寫一個工具類级野,專門用于關(guān)閉Closeable接口的實(shí)現(xiàn)。

public final class CloseUtils {
    private CloseUtils() {
    }

    public static void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

我們把這個工具類引用到上述的例子中看看效果:

public void put(String url, Bitmap bitmap) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            CloseUtils.closeQuietly(fileOutputStream);
        }
    }

是不是簡潔多了粹胯!而且這個closeQuietly方法可以運(yùn)用到各類可關(guān)閉的對象中蓖柔,保證了代碼的重用性。我們可以想想风纠,為什么close方法定義在FileOutputStream(或者它的父類)中况鸣,而是單獨(dú)用一個接口承載這個方法呢?從使用者的角度來看竹观,也就是這個CloseUtils工具類镐捧,其實(shí)只關(guān)心close方法,而不關(guān)心FileOutputStream的其他方法臭增,如果沒有這樣一個Closeable接口懂酱,closeQuietly(Closeable closeable) 方法的形參就得定義成FileOutputStream,會將沒有必要的其他方法暴露出來誊抛,并且其他同樣擁有close方法的類無法使用closeQuietly來關(guān)閉玩焰。

想一想Android中的OnClickListener以及OnLongClickListener,雖然都是點(diǎn)擊芍锚,但是并沒有定義在一個接口中昔园,而是分為兩個蔓榄,因?yàn)楹芏鄷r候,用戶只關(guān)心其中的一種默刚。

迪米特原則

Law of Demeter 或者也叫做最少知識原則(Least Konwledge Principe)

定義:一個對象應(yīng)該對其他對象有最少的了解甥郑。通俗點(diǎn)說,一個類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少荤西,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒有關(guān)系澜搅。類與類之間的關(guān)系越密切,耦合度越大邪锌,當(dāng)一個類發(fā)生改變時勉躺,對另一個類的影響也越大。
下面我們用租房為例來講講迪米特原則的應(yīng)用觅丰。
房間:

public class Room {
    public float area;
    public float price;

    public Room(float area, float price) {
        this.area = area;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Room [area=" + area + ",price=" + price + "]";
    }
}

房間:

public class Mediator {
    List<Room> mRooms = new ArrayList<>();

    public Mediator() {
        for (int i = 0; i < 5; i++) {
            mRooms.add(new Room(14 + i, (14 + i) * 1500));
        }
    }

    public List<Room> getAllRooms() {
        return mRooms;
    }
}

租客:

public class Tenant {
    public float roomArea;
    public float roomPrice;
    public static final float diffPrice = 100.0001f;
    public static final float diffArea = 0.00001f;

    public void rentRoom(Mediator mediator) {
        List<Room> allRooms = mediator.getAllRooms();
        for (Room room : allRooms) {
            if (isSuitable(room)) {
                System.out.println("租到房間啦饵溅!" + room);
            }
        }
    }

    private boolean isSuitable(Room room) {
        return Math.abs(room.price - roomPrice) < diffPrice
                && Math.abs(room.area - roomArea) < diffArea;
    }
}

從代碼中可以看到,Tenant不僅依賴了Mediator類妇萄,還需要頻繁地與Room類打交道蜕企。如果把檢測條件都放在Tenant類中,那么中介類的功能就被弱化了冠句,導(dǎo)致Tenant與Room的耦合轻掩,因?yàn)門enant必須知道許多關(guān)于Room的細(xì)節(jié),當(dāng)Room變化時Tenant也必須跟著變化懦底。就像下面UML描述的那樣:

image

既然耦合太嚴(yán)重了唇牧,那我們就只能解耦了。首先要明確的時聚唐,我們只和必要的類通信奋构,即移除Tenant與Room的依賴。我們進(jìn)行如下修改:

public class Mediator {
    List<Room> mRooms = new ArrayList<>();

    public Mediator() {
        for (int i = 0; i < 5; i++) {
            mRooms.add(new Room(14 + i, (14 + i) * 1500));
        }
    }

    public Room rentOut(float area, float price) {
        for (Room room : mRooms) {
            if (isSuitable(area, price, room)) {
                return room;
            }
        }
        return null;
    }

    private boolean isSuitable(float area, float price, Room room) {
        return Math.abs(room.price - price) < Tenant.diffPrice
                && Math.abs(room.area - area) < Tenant.diffArea;
    }
}
public class Tenant {
    public float roomArea;
    public float roomPrice;
    public static final float diffPrice = 100.0001f;
    public static final float diffArea = 0.00001f;

    public void rentRoom(Mediator mediator) {
        System.out.println("租到房間啦拱层!" + mediator.rentOut(roomArea, roomPrice));
    }
}

重構(gòu)后的UML圖如下:

image

只是將對Room的判定操作移到了Mediator類中弥臼,這本應(yīng)該是Mediator的職責(zé),根據(jù)租戶設(shè)定的條件查找符合要求的房子根灯,并且將結(jié)果交給租戶就可以了径缅。租戶并不需要知道太多關(guān)于Room的細(xì)節(jié)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烙肺,一起剝皮案震驚了整個濱河市纳猪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桃笙,老刑警劉巖氏堤,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搏明,居然都是意外死亡鼠锈,警方通過查閱死者的電腦和手機(jī)闪檬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來购笆,“玉大人粗悯,你說我怎么就攤上這事⊥罚” “怎么了样傍?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铺遂。 經(jīng)常有香客問我衫哥,道長,這世上最難降的妖魔是什么襟锐? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任撤逢,我火速辦了婚禮,結(jié)果婚禮上捌斧,老公的妹妹穿的比我還像新娘。我一直安慰自己泉沾,他們只是感情好捞蚂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跷究,像睡著了一般姓迅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俊马,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天丁存,我揣著相機(jī)與錄音,去河邊找鬼柴我。 笑死解寝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的艘儒。 我是一名探鬼主播聋伦,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼界睁!你這毒婦竟也來了觉增?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤翻斟,失蹤者是張志新(化名)和其女友劉穎逾礁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體访惜,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘹履,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年腻扇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片植捎。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡衙解,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焰枢,到底是詐尸還是另有隱情蚓峦,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布济锄,位于F島的核電站暑椰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏荐绝。R本人自食惡果不足惜一汽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望低滩。 院中可真熱鬧召夹,春花似錦、人聲如沸恕沫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婶溯。三九已至鲸阔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間迄委,已是汗流浹背褐筛。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叙身,地道東北人渔扎。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像信轿,于是被迫代替她去往敵國和親赞警。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

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