設計模式六大原則

本文是《Android源碼設計模式解析與實戰(zhàn)》第一章讀書筆記

一、單一原則

單一原則的英文是 Single Responsibility Principle,縮寫是 SRP 。SRP 的定義是:就一個類而言,應該僅有一個引起它變化的原因。ImageLoader的最初版本:

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

    private void initImageCache() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.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);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

}

最初的版本所有的代碼都寫在一個類里面,隨著功能增加會越來越大,應該把ImageLoader拆分開來,各個功能獨立出來,滿足單一職責原則

public class ImageLoader {
    //圖片緩存
    ImageCache mImageCache = new ImageCache();
    //線程池,線程數(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);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

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

    public ImageCache() {

    }

    private void initImageCache() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
    }

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

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

}

這樣一拆分,ImageLoader的功能基本滿足了單一職責原則,分成了兩塊,圖片加載和圖片緩存,這樣緩存邏輯如果要修改時,就不需要修改ImageLoader了。

單一職責原則的使用一定要根據(jù)實際業(yè)務來,不能過度使用,拆分的太細。

讓程序更靈活——開閉原則

開閉原則英文全稱是 Open Close Principle,縮寫是 OCP。開閉原則的定義是:軟件中的對象(類、模塊、函數(shù)等)應該對于擴展是開放的,對于修改是封閉的。定義讀起來比較抽象,我們還是以上面的ImageLoader的例子來理解。經(jīng)過第一輪重構(gòu),ImageLoader職責單一、結(jié)構(gòu)清晰,但有一個問題是,我們的緩存只有一種內(nèi)存緩存,每次應用重新打開就沒有緩存了。所以打算引入 SD 緩存。DiskCache.java類的代碼如下:

public class DiskCache {
    static String cacheDir = "sdcard/cache/";

    //從緩存中取圖片
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }

    //將圖片緩存到SD卡
    public void put(String url, Bitmap bmp) {
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(cacheDir + url);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

加了 SD 卡緩存后,ImageLoader類也有所更新:

public class ImageLoader {
    //內(nèi)存緩存
    ImageCache mImageCache = new ImageCache();

    //SD卡緩存
    DiskCache mDiskCache = new DiskCache();

    boolean isUserDiskCache = false;

    //線程池,線程數(shù)量為 CPU 的數(shù)量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
    public void displayImage(final String url, final ImageView imageView) {
        //判斷使用哪種緩存
        Bitmap bitmap = isUserDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        
        ...
    }

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

}

這個版本的修改,用戶需要設置一個變量來選擇用內(nèi)存還是SD卡的緩存方式。雖然增加了一個類,但我們還要去改動ImageLoader,如果有多個緩存方式的話,按這種方式ImageLoader中的條件判斷會變得很復雜。
而且這個版本用戶只能二選一,如果用戶有這種需求,優(yōu)先使用內(nèi)存緩存,內(nèi)存緩存中沒有再使用SD卡緩存,SD卡沒有通過網(wǎng)絡加載圖片。于是有了一個雙緩存類DoubleCache.java。

public class DoubleCache {
    //內(nèi)存緩存
    ImageCache mMemoryCache = new ImageCache();

    //SD卡緩存
    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 bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}

這時的ImageLoader也要做如下修改:

        if (isUseDoubleCache) {
            bitmap = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bitmap = mDiskCache.get(url);
        } else {
            mImageCache.get(url);
        }

代碼寫到這我們發(fā)現(xiàn),每一次新加一個緩存都要去修改ImageLoader中的條件判斷,非常的麻煩,還有可能引入Bug??蓴U展性比較差。
“軟件中的對象(類、模塊、函數(shù)等)應該對于擴展是開放的,但是對于修改是封閉的,這就是開放——關閉原則。也就是說,當軟件需要變化時,我們應該盡量通過擴展的方式來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)?!?br> 所以下面對ImageLoader再一次重構(gòu):

public class ImageLoader {

    ImageCache mImageCache = new MemoryCache();

    //線程池,線程數(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);
            }
        });
    }

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

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;

        try {
            URL url = new URL(imageUrl);
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

}

這里的ImageCache已經(jīng)不是原來的那個類了,這次重構(gòu)把它提取成一個接口:

public interface ImageCache {
    Bitmap get(String url);
    void put(String url, Bitmap bmp);
}

接口定義了兩個函數(shù),MemoryCache、DiskCache、DoubleCache都實現(xiàn)了該接口。ImageLoader中的默認緩存方式是內(nèi)存緩存,用戶想使用別的只要調(diào)用 setImageCache 就可以。也可以自定義 ImageCache 的實現(xiàn)。
開閉原則指導我們,當軟件需要變化時,應該盡量通過擴展的方式來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)。

構(gòu)建擴展性——里氏替換原則

里氏替換原則英文全稱是 Liskov Substiution Primciple,縮寫是 LSP。LSP 通俗定義是:所有引用基類的地主必須能透明的使用其子類對象。其實最終總結(jié)就兩個字:抽象。
MemoryCache、DiskCache、DoubleCache都可以替換 ImageCache的工作,這很好的反應了里氏替換原則,并且能夠保證行為的正確性。ImageCache 建立了獲取緩存圖片、保存緩存圖片的接口規(guī)范,MemoryCache 等根據(jù)接口規(guī)范實現(xiàn)了相應的功能,用戶只需要在使用時指定具體的緩存對象就可以動態(tài)地替換 ImageCache 中的緩存策略。這就使得 ImageLoader 有了無限的可能性,也就是保證了可擴展性。

讓項目擁有變化的能力——依賴倒置原則

依賴倒置原則英文全稱是 Dependence Inversion Principle,縮寫是 DIP。DIP的幾個關鍵點:
1、高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象;
2、抽象不應該依賴細節(jié);
3、細節(jié)應該依賴抽象。
在 Java 中抽象就是指接口或抽象類,兩者都是不能直接被實例化的;細節(jié)就是實現(xiàn)類,實現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細節(jié),其特點就是,可以直接被實例化。依賴倒置原則在 Java 語言中的表現(xiàn)就是:模塊間的依賴通過抽象發(fā)生,實現(xiàn)類之間不發(fā)生直接的依賴關系,其依賴關系是通過接口或抽象類發(fā)生的。

更高的靈活性——接口隔離原則

接口隔離原則英文全稱是 Interface Segregation Principles,縮寫是 ISP。ISP的定義是:客戶端不應該依賴它不需要的接口。另一種定義是:類間的依賴關系應該建立在最小的接口上。

總結(jié)以上的5點原則就是單一職責、開閉原則、里氏替換、接口隔離以及依賴倒置,這5大原則被稱為SOLID。

更好的可擴展性——迪米特原則

定義:一個對象應該對其他對象有最少的了解。通俗地講,一個類應該對自己需要耦合或調(diào)用的類知道得最少,類的內(nèi)部如何實現(xiàn)與調(diào)用者或者依賴者沒關系,調(diào)用者或者依賴者只需要知道它需要的方法即可,其它可一概不用管。類與類之間的關系越密切,耦合度越大,當一個類發(fā)生改變時,對另一個類的影響也越大。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 本文出自《Android源碼設計模式解析與實戰(zhàn)》中的第一章。 1、優(yōu)化代碼的第一步——單一職責原則 單一職責原則的...
    MrSimp1e0閱讀 1,911評論 1 13
  • 設計模式六大原則 設計模式六大原則(1):單一職責原則 定義:不要存在多于一個導致類變更的原因。通俗的說,即一個類...
    viva158閱讀 821評論 0 1
  • 轉(zhuǎn)載標注聲明:http://www.uml.org.cn/sjms/201211023.asp 目錄:[設計模式六...
    Bloo_m閱讀 796評論 0 7
  • 前言 設計模式六大原則網(wǎng)上資料比較多比較亂,本文將網(wǎng)上的一些好的資料做一下整理,以便隨時翻閱。友情提示,設計模式雖...
    簡單的土豆閱讀 1,509評論 0 10
  • 什么是設計模式?設計模式(Design Pattern)是一套被反復使用、多數(shù)人知曉的、經(jīng)過分類的、代碼設計經(jīng)驗的...
    星星_點燈閱讀 493評論 0 0

友情鏈接更多精彩內(nèi)容