在軟件設計模式中滚局,這種不能修改工猜,但可以擴展的思想是重要的一種設計原則,是開放—封閉原則(The Open-Closeed Principle币狠,簡稱OCP)或叫開-閉原則游两。
開放—封閉原則
的定義是:軟件中的對象(類、模塊漩绵、函數(shù)等等)應該對于擴展是開放的贱案,對于修改是封閉的。
在單一職責原則一文中我們寫了 ImageLoader 圖片加載類止吐,現(xiàn)在我們引入SD卡緩存宝踪,這樣下載過的圖片就會緩存到本地
/**
* 圖片緩存到SD卡中
*/
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 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();
}
}
}
}
}
因為需要將圖片緩存到SD卡中侨糟,ImageLoader 代碼有所更新,具體代碼如下瘩燥。
/**
* 圖片加載器
*/
public class ImageLoader {
/**
* 圖片緩存
*/
ImageCache mImageCache;
/**
* SD卡緩存
*/
DiskCache mDiskCache=new DiskCache();
/**
* 是否使用SD卡緩存
*/
boolean isUseDiskCache=false;
/**
* 線程池秕重,線程數(shù)量為CPU數(shù)量
* Runtime.getRuntime().availableProcessors() 方法:返回可用處理器的Java虛擬機的數(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)) {
updateImageView(imageView, bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
public void useDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
}
從上述代碼中可以看到厉膀,新增了一個 DiskCache 類和往 ImageLoader 類中加入了少量代碼就添加了 SD 卡緩存的功能溶耘,用戶可以通過 useDiskCache 方法來對使用哪種緩存進行設置,如:
ImageLoader imageLoader=new ImageLoader();
//使用SD卡緩存
imageLoader.useDiskCache(true);
//使用內(nèi)存緩存
imageLoader.useDiskCache(false);
有個問題就是每次加入新的緩存方法時都要修改原來的代碼服鹅,這樣很可能引入bug凳兵,而且會使原來的代碼邏輯越來越復雜。
軟件中的對象(類企软、模塊庐扫、函數(shù)等)應該對于擴展是開放的,對于修改是封閉的澜倦,這就是開放—關閉原則聚蝶。也就是說,當軟件需求變化時藻治,應該盡量通過擴展的方式來實現(xiàn)變化碘勉,而不是通過修改已有的代碼來實現(xiàn)。
下面對 ImageLoader 重構桩卵,具體代碼如下验靡。
/**
* 圖片加載器
*/
public class ImageLoader {
/**
* 圖片緩存
*/
ImageCache mImageCache = new MemoryCache();
/**
* 線程池,線程數(shù)量為CPU數(shù)量
* Runtime.getRuntime().availableProcessors() 方法:返回可用處理器的Java虛擬機的數(shù)量雏节。
*/
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
/**
* UI Handler
*/
Handler mUiHandler = new Handler(Looper.getMainLooper());
/**
* 注入緩存實現(xiàn)
*/
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 imageUrl, final ImageView imageView) {
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
updateImageView(imageView, bitmap);
}
mImageCache.put(imageUrl, 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 (Exception e) {
e.printStackTrace();
}
return bitmap;
}
private void updateImageView(final ImageView imageView, Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
}
這里把 ImageCache 提取為一個圖片緩存的接口胜嗓,用來抽象圖片緩存的功能,接口聲明如下:
/**
* 圖片緩存接口钩乍,用來抽象圖片緩存的功能
*/
public interface ImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bitmap);
}
ImageCache 接口定義了獲取辞州、緩存圖片兩個函數(shù),緩存的key是圖片的url寥粹,值是圖片本身变过。內(nèi)存緩存、SD卡緩存涝涤、雙緩存都實現(xiàn)了該接口媚狰,我們看看這幾個緩存實現(xiàn)。
/**
* 內(nèi)存緩存MemoryCache類
* 圖片的內(nèi)存緩存,key為圖片的uri,值為圖片本身
*/
public class MemoryCache implements ImageCache {
private LruCache<String, Bitmap> mMemoryCache;
public MemoryCache() {
//初始化LRU緩存
// 計算可使用的最大內(nèi)存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取4分之一的可用內(nèi)存作為緩存
final int cacheSize = maxMemory / 4;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
//int size = bitmap.getRowBytes() * bitmap.getHeight();
//獲取大小,Bitmap所占用的內(nèi)存空間數(shù)等于Bitmap的每一行所占用的空間數(shù)乘以Bitmap的行數(shù)
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);
}
}
/**
* SD卡緩存 DiskCache 類
* 圖片緩存到SD卡中
*/
public class DiskCache implements ImageCache {
static String cacheDir = "sdcard/cache/";
/**
* 從本地文件中獲取圖片
*/
@Override
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
/**
* 將bitmap寫入文件中
*/
@Override
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();
}
}
}
}
}
/**
* 雙緩存 DoubleCache 類
*/
public class DoubleCache implements ImageCache {
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();
/**
* 先從緩存中獲取圖片阔拳,如果沒有崭孤,在從SD卡中獲取
*/
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
/**
* 將圖片緩存到內(nèi)存和SD卡中
*/
@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}
我們在 ImageLoader 類中增加了一個setImageCache(ImageCache imageCache)函數(shù),用戶可以通過該函數(shù)設置緩存實現(xiàn),也就是通常說的依賴注入辨宠。設置緩存實現(xiàn)的方式如下:
ImageLoader imageLoader = new ImageLoader();
//使用內(nèi)存緩存
imageLoader.setImageCache(new MemoryCache);
//使用 SD 卡緩存
imageLoader.setImageCache(new DiskCache);
//使用雙緩存
imageLoader.setImageCache(new DoubleCache);
//使用自定義圖片緩存
imageLoader.setImageCache(new ImageCache(){
@Override
public void put(String url, Bitmap bitmap) {
//緩存圖片
}
@Override
public Bitmap get(String url) {
/* 從緩存中獲取圖片 */
return null;
}
});
在上述代碼中遗锣,通過 setImageCache(ImageCache imageCache) 方法注入不同的緩存實現(xiàn),這樣不僅能夠使 ImageLoader 更簡單彭羹、健壯黄伊,也使 ImageLoader 的可擴展性、靈活性更高派殷。MemoryCache还最、DiskCache、DoubleCache 緩存圖片的具體實現(xiàn)完全不一樣毡惜,但它們都實現(xiàn)了 ImageCache 接口拓轻。當用戶需要自定義實現(xiàn)緩存策略時,只需新建一個實現(xiàn) ImageCache 接口的類经伙,然后構造該類的對象扶叉,并且通過 setImageCache(ImageCache imageCache) 注入到ImageLoader 中,這樣 ImageLoader 就實現(xiàn)了千變?nèi)f化的緩存策略帕膜,切擴展這些緩存策略并不會導致 ImageLoader 類的修改枣氧。