1.簡稱
開閉原則的全稱是Open close Principle ,縮寫是OCP
2.定義
軟件中的對象(類龄句、模塊傀蚌、函數(shù)等)應該 對于擴展是開放的勋桶,對于修改是封閉的脱衙。
3.問題
在軟件的生命周期內,因為變化例驹、升有代碼進級和維護等原因需要對軟件原行修改時捐韩,可能會將錯誤引入原本已經(jīng)測試過的舊版本中,破環(huán)原有系統(tǒng)鹃锈。
4.解決
盡量使用擴展的方式實現(xiàn)變化荤胁,但在實際開發(fā)中往往修改原有代碼、擴展代碼同時進行仪召。
舉例:
內存緩存
public class ImageCache {
LruCache<String,Bitmap> mImageCache;//圖片LRU緩存
public ImageCache(){
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 put(String url,Bitmap bitmap){
mImageCache.put(url,bitmap);
}
public Bitmap get(String url){
return mImageCache.get(url);
}
}
SD卡緩存
public class DiskCache {
static String cacheDir ="sdcard/cache/";
//從緩存中獲取圖片
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 (Exception e){
e.printStackTrace();
}finally {
if(fileOutputStream!=null){
try {
fileOutputStream.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
雙緩存
public class DoubleCache {
ImageCache mMemoryCache = new ImageCache();
DiskCache mDiskCache =new DiskCache();
public void put(String url,Bitmap bitmap){
mMemoryCache.put(url,bitmap);
mDiskCache.put(url,bitmap);
}
//先從內存緩存中獲取圖片寨蹋,如果沒有,再從SD卡中獲取
public Bitmap get(String url){
Bitmap bitmap =mMemoryCache.get(url);
if(bitmap==null){
bitmap =mDiskCache.get(url);
}
return bitmap;
}
}
ImageLoader類
public class ImageLoader {
ImageCache mImageCache =new ImageCache();//圖片緩存
DiskCache mDiskCache =new DiskCache();//sd卡緩存
DoubleCache mDoubleCache =new DoubleCache();//雙緩存
boolean isUseDiskCache =false;//是否使用SD卡緩存
boolean isUseDoubleCache =false;//是否使用雙緩存
//線程池 線程數(shù)量為CPU的數(shù)量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private 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);
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 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;
}
public void useDiskCache(boolean useDiskCache){
isUseDiskCache =useDiskCache;
}
public void useDoubleCache(boolean useDoubleCache){
isUseDoubleCache =useDoubleCache;
}
}
上面的代碼可以自由控制使用內存緩存扔茅,SD卡緩存已旧,雙緩存,看似挺好的召娜,但是有一個問題运褪,每次在程序中加入新的緩存實現(xiàn)時都需要修改ImageLoader類,然后通過布爾值讓用戶選擇使用哪種緩存玖瘸,因此在ImageLoader中存在各種if-else判斷語句秸讹,通過這些判斷來確定使用哪些緩存。隨著邏輯的引入雅倒,代碼變得越來越復雜璃诀,某一個if條件寫錯就要花很長時間排除。另外用戶不能自己實現(xiàn)緩存注入到ImageLoader中蔑匣,可擴展性差劣欢。
怎么修改呢棕诵?
可以通過定義一個接口,里面為公用方法凿将,然后讓所有緩存對象實現(xiàn)這個接口校套,在ImageLoader中定義一個方法setImageCache,用到哪個緩存類就傳入哪個緩存類牧抵,自由度高笛匙。
public class ImageLoader {
ImageCache mImageCache = new MemoryCache();//圖片緩存
//注入緩存實現(xiàn)
public void setImageCache(ImageCache cache) {
mImageCache = cache;
}
//線程池 線程數(shù)量為CPU的數(shù)量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
submitLoadRequest(url, imageView);
}
/**
* 緩存中沒圖片 則從網(wǎng)絡下載
*
* @param url
* @param 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);
}
});
}
public 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;
}
}
注意這里的ImageCache是一個接口,并不是前面的類犀变,主要用來抽象圖片緩存的功能妹孙。緩存的key是圖片的url.值是圖片的本身。
public interface ImageCache {
public Bitmap get(String url);
public void put(String url,Bitmap bitmap);
}
內存緩存获枝,sd卡緩存涕蜂,雙緩存都實現(xiàn)了該接口。
//內存緩存類
public class MemoryCache implements ImageCache{
private LruCache<String,Bitmap> mMemoryCache;
public MemoryCache(){
initImageCache();
}
@Override
public Bitmap get(String url) {
return mMemoryCache.get(url);
}
@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url,bitmap);
}
private void initImageCache() {
final int maxMemory =(int)(Runtime.getRuntime().maxMemory()/1024);
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;
}
};
}
}
//sd卡緩存
public class DiskCache implements ImageCache{
static String cacheDir ="sdcard/cache/";
//從緩存中獲取圖片
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 (Exception e){
e.printStackTrace();
}finally {
if(fileOutputStream!=null){
try {
fileOutputStream.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
//雙緩存
public class DoubleCache implements ImageCache{
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache =new DiskCache();
//先從內存緩存中獲取圖片映琳,如果沒有,再從SD卡中獲取
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);
}
}
那么怎樣設置實現(xiàn)緩存呢蜘拉?
ImageLoader imageLoader =new ImageLoader();
//使用內存緩存
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 cache) 方法注入不同的緩存實現(xiàn)萨西,這樣能夠使ImageLoader更簡單,健壯旭旭,也使得ImageLoader的擴展性谎脯、靈活性更高。MemoryCache持寄、DiskCache源梭、DoubleCache緩存圖片的具體實現(xiàn)完全不一樣,但是稍味,它們都實現(xiàn)了ImageCache接口废麻。當用戶需要自定義緩存策略時,只需要新建一個實現(xiàn)ImageCache的接口的類模庐,然后構造該類的對象烛愧,并且通過setImageCache(ImageCache cache)注入到ImageLoader中,這樣ImageLoader就實現(xiàn)了千變萬化的緩存策略掂碱,且擴展這些緩存策略并不會導致ImageLoader類的修改怜姿。
總結
當需求發(fā)生變化時,應該盡量通過擴展的方式來實現(xiàn)變化疼燥,而不是修改原有代碼來實現(xiàn)沧卢,盡量遵循開閉原則。
開閉原則的優(yōu)點:
1.通過擴展已有的軟件系統(tǒng)醉者,可以提供新的行為但狭,以滿足對軟件的新需求披诗,使變化中的軟件系統(tǒng)有一定的適應性和靈活性。
2.已有的軟件模塊熟空,特別是最重要的抽象層模塊不能再修改藤巢,這就使變化中的軟件系統(tǒng)有一定的穩(wěn)定性和延續(xù)性。
3.增加復用性和可維護性息罗。