Android開發設計模式六大原則之開閉原則
開閉原則定義:軟體中的物件(類,模組,函式等)應該對於擴充套件是開放的,對於修改是關閉的。英文全稱(Open Close Principle),簡稱:OCP
在實際開發中常常會遇到這樣的問題,從別人那裡接手過來的程式碼,還沒來的及熟悉程式碼,專案就催著趕緊升級,當你想使用一個功能的時候,你可能發現專案裡有相關程式碼,但是你又不敢用,你怕改出來問題,所以一般可能都會採取新增一塊功能一樣的程式碼。其實這就是簡單的遵循了開閉原則。
上面的場景相信大家都遇見過,你不想你寫的程式碼,被別人詬病吧。所以掌握開閉原則要領很有必要性。
圖片的載入相信大家是避免不了的一個功能。在圖片的載入顯示過程中,圖片的快取是必不可少的。首先我們定義一個圖片快取類:
//圖片快取類 class ImageCache{ //圖片LRU快取 LruCache<String,Bitmap> mImageCache; public ImageCache(){ //初始化記憶體 } //快取圖片 public void put(String url,Bitmap bitmap){ mImageCache.put(url,bitmap); } //快取圖片 public Bitmap get(String url){ mImageCache.get(url); } } //將圖片載入ImageView顯示類 class ImageLoader{ //記憶體快取 ImageCache mImageCache = new ImageCache(); //執行緒池 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; } //如果記憶體沒有則根據URL下載圖片 imageView.setTag(url);//給imageview設定TAG 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); } }) } }
這樣的實現過程,載入顯示圖片沒什麼問題。但是在實際使用過程中,一般會要求,圖片不單要快取在記憶體中,還要快取在本地。這樣即使關掉了應用下一次開啟的時候,已經下載過的圖片就不需要去耗費流量再次下載了,直接從本地讀取,同時快取到記憶體和顯示圖片。那接下來,我們就得新增一種圖片快取的方法:
//sd卡圖片快取類 public class DiskCache{ static String cacheDir = "sdcard/cache/"; //從本地快取中獲取圖片 public Bitmap get(String localUri){...} //將圖片快取到本地 public void put(String localUri, Bitmap bmp){...} } 然後更新ImageLoader類中的程式碼: //將圖片載入ImageView顯示類 class ImageLoader{ //記憶體快取 ImageCache mImageCache = new ImageCache(); //新增程式碼 SD卡快取 DiskCache mDiskCache = new DiskCache(); //執行緒池 ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //載入圖片 public void displayImage(final String url,final ImageView imageView){ //先從記憶體獲取 Bitmap bitmap = mImageCache.get(url); //新增程式碼 if(bitmap == null){ //記憶體沒有,從SD卡獲取 bitmap = mDiskCache.get(url); }else{ //其他快取方式 ... } if(bitmap != null){ imageView.setImageBitmap(bitmap); return; } //如果記憶體、SD卡均沒有則根據URL下載圖片 imageView.setTag(url);//給imageview設定TAG 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); //新增程式碼,快取到SD卡 mDiskCache.put(url,bitmap); } }) } }
這樣就完成了新增快取到SD卡。你會發現,每次新增快取方式,都會修改原來的載入類程式碼。這樣很可能會引入Bug,就會發生,之前用的好好的功能,可能就突然不好用了。
知道了開閉原則之後,我們可以這樣想:對擴充套件開放,對修改關閉。那麼首先你的載入類得支援可擴充套件,而且無需修改即可被別人拿來使用。
說到擴充套件,我們一般會使用介面來定義,所有的擴充套件都要實現接口裡定義的功能。這裡我們說的是擴充套件快取方式,那自然要將快取定義成為介面。
//圖片快取介面宣告 public interface ImageCache{ /** * 獲取圖片 */ public Bitmap get(String url); /** * 快取圖片 */ public void put(String url,Bitmap bitmap); }
so easy ! 不過多說明。
定義好了介面,那麼接下來,每一個擴充套件方式都要實現該介面,這也起到了一個約束的作用:
//擴充套件記憶體快取類 實現 ImageCache 介面 public class MemoryCache implements ImageCache{ //圖片LRU快取 private LruCache<String,Bitmap> mMemoryCache; public ImageCache(){ //初始化記憶體 } //快取圖片 @Override public void put(String url,Bitmap bitmap){ mImageCache.put(url,bitmap); } //快取圖片 @Override public Bitmap get(String url){ mImageCache.get(url); } } //擴充套件SD卡快取類 實現 ImageCache 介面 public class DiskCache implements ImageCache{ //快取圖片 @Override public void put(String url,Bitmap bitmap){ //將圖片存入本地檔案中 ... } //快取圖片 @Override public Bitmap get(String url){ //根據url從本地讀取圖片 ... } }
我只實現了兩種快取方式,你也可以依葫蘆畫瓢,實現雙快取,就是我上面說的預設支援記憶體快取和本地快取。
那麼接下來我們應該怎麼修改ImageLoader類呢,很簡單,首先你要明白,既然所有的快取方式都擴充套件自ImageCache ,那麼所有的擴充套件類又可以稱之為一個ImageCache。明白這一點,那下面的程式碼就不難理解了。
//將圖片載入ImageView顯示類 class ImageLoader{ //如果不setImageCache 預設支援記憶體快取 ImageCache mImageCache = new MemoryCache(); //執行緒池 ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //通過set的方式注入自定義擴充套件的快取 public void setImageCache(ImageCache cache){ mImageCache = cache; } //載入圖片 public void displayImage(final String url,final ImageView imageView){ //先從記憶體獲取 Bitmap bitmap = mImageCache.get(url); if(bitmap != null){ imageView.setImageBitmap(bitmap); return; } //如果記憶體沒有則根據URL下載圖片 imageView.setTag(url);//給imageview設定TAG 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); } }) } }
可以看到,ImageLoader中多了setImageCache()這個方法,這是讓ImageLoader按照你自定義的擴充套件快取方法進行工作的重要途徑。因為有了這個入口,你就可以像下面這樣,使用你擴充套件的快取方式,而且不用修改已有的載入類。
ImageLoader imageloader = new ImageLoader(); //設定你自己擴充套件的記憶體快取 imageloader.setImageCache(new MemoryCache); //或者設定你自己擴充套件的本地存快取 imageloader.setImageCache(new DiskCache); //再或者設定你自己擴充套件的其他存快取方式 imageloader.setImageCache(new OtherCache); //再再或者,你可以直接在呼叫的時候,直接自定義快取方式 imageloader.setImageCache(new ImageCache(){ @Override public void put(String url,Bitmap bitmap) ... } @Override public Bitmap get(String url) ... } );
接下里再看開閉原則的UML圖是不是就能看懂了:
好了,開閉原則內容記錄到此結束。特此發出來,與大家將我的理解分享,希望不會誤導別人。
寫在最後:改變世界,先從改變自己開始。--- 我說的
你也可以關注我的公眾號: 碼H.cam , 隨時隨地學習設計模式