本文是《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ā)生改變時,對另一個類的影響也越大。