面向對象的六大原則(二)-- 開閉原則(切合Android,ImageLoader)

二:開閉原則(Open Close Principle)

定義:軟件中的對象(類、模塊、函數等)應該對於擴展是開放的,但是對於修改時封閉的。(雖然在實戰中修改原有代碼,擴展代碼往往是難以避免的)
遵循開閉原則的重要手段應該是通過抽象!!!

承接與上一篇文章,單一職責原則:點擊打開鏈接

問題代碼:
ImageLoader.java
/**
 * 圖片加載類
 */
public  class ImageLoader {
    // 圖片緩存
    ImageCache mImageCache = new ImageCache() ;
    // 線程池,線程數量爲CPU的數量
    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  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.java
public class ImageCache {
    // 圖片LRU緩存
    LruCache<String, Bitmap> mImageCache;

    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) ;
    }
}

以上代碼雖然功能分離了,但是每次打開應用都要重新下載圖片,非常耗費流量,而且下載圖片也是一個耗時操作。這簡直不能忍。。。

於是加入DiskCache.java
public class DiskCache {
    // 爲了簡單起見臨時寫個路徑,在開發中請避免這種寫法 !
    static String cacheDir = "sdcard/cache/";
     // 從緩存中獲取圖片
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }
 
    // 將圖片緩存到內存中
    public  void  put(String url, Bitmap bmp) {
       FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bmp.compress(CompressFormat.PNG, 100, fileOutputStream);
      } catch (FileNotFoundException e) {
            e.printStackTrace();
      } final ly {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
              } catch (IOException e) {
                    e.printStackTrace();
             }
          }
      }
    }
}
因爲需要將圖片緩存到SD卡中,所以,ImageLoader代碼有所更新,具體代碼如下:
public class ImageLoader {
    // 內存緩存
    ImageCache mImageCache = new ImageCache();
    // SD卡緩存
    DiskCache mDiskCache = new DiskCache();
    // 是否使用SD卡緩存
    boolean isUseDiskCache = false;
    // 線程池,線程數量爲CPU的數量
    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;
       }
        // 沒有緩存,則提交給線程池進行下載
    }
 
    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache ;
    }
}
 

新增了一個DiskCache類和往ImageLoader類中加入了少量代碼就添加了SD卡緩存的功能,用戶可以通過useDiskCache方法來對使用哪種緩存進行設置,例如:
ImageLoader imageLoader = new ImageLoader() ;
 // 使用SD卡緩存
imageLoader.useDiskCache(true);
// 使用內存緩存
imageLoader.useDiskCache(false);
 

但是!!!有些明顯的問題,就是使用內存緩存時用戶就不能使用SD卡緩存,類似的,使用SD卡緩存時用戶就不能使用內存緩存。用戶需要這兩種策略的綜合,首先緩存優先使用內存緩存,如果內存緩存沒有圖片再使用SD卡緩存,如果SD卡中也沒有圖片最後才從網絡上獲取,這纔是最好的緩存策略。
/**
 * 雙緩存。獲取圖片時先從內存緩存中獲取,如果內存中沒有緩存該圖片,再從SD卡中獲取。
 *  緩存圖片也是在內存和SD卡中都緩存一份
 */
public class DoubleCache {
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();
 
    // 先從內存緩存中獲取圖片,如果沒有,再從SD卡中獲取
    public   Bitmap get(String url) {
       Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
       }
        return  bitmap;
    }
 
    // 將圖片緩存到內存和SD卡中
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
   }
}

再看看最新的ImageLoader類吧,代碼更新也不多:
public class ImageLoader {
    // 內存緩存
    ImageCache mImageCache = new ImageCache();
    // SD卡緩存
    DiskCache mDiskCache = new DiskCache();
    // 雙緩存
    DoubleCache mDoubleCache = new DoubleCache() ;
    // 使用SD卡緩存
    boolean isUseDiskCache = false;
    // 使用雙緩存
    boolean isUseDoubleCache = false;
    // 線程池,線程數量爲CPU的數量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
 
 
    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bmp = null;
         if (isUseDoubleCache) {
            bmp = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bmp = mDiskCache.get(url);
        } else {
            bmp = mImageCache.get(url);
        }
 
         if ( bmp != null ) {
            imageView.setImageBitmap(bmp);
        }
        // 沒有緩存,則提交給線程池進行異步下載圖片
    }
 
    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache ;
    }
 
    public void useDoubleCache(boolean useDoubleCache) {
        isUseDoubleCache = useDoubleCache ;
    }
}

這樣做固然有些得意,問題依舊有:每次在程序中加入新的緩存實現時都需要修改ImageLoader類,然後通過一個布爾變量來讓用戶使用哪種緩存,因此,就使得在ImageLoader中存在各種if-else判斷,通過這些判斷來確定使用哪種緩存。隨着這些邏輯的引入,代碼變得越來越複雜、脆弱,如果小民一不小心寫錯了某個if條件(條件太多,這是很容易出現的),那就需要更多的時間來排除。整個ImageLoader類也會變得越來越臃腫。最重要的是用戶不能自己實現緩存注入到ImageLoader中,可擴展性可是框架的最重要特性之一。

於是通過抽象來達到程序的可擴展性,如圖所示:


具體實現:
ImageLoader.java
public class ImageLoader {
    // 圖片緩存
    ImageCache mImageCache = new MemoryCache();
    // 線程池,線程數量爲CPU的數量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
 
    // 注入緩存實現
    public void setImageCache(ImageCache cache) {
        mImageCache = cache;
    }
 
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        // 圖片沒緩存,提交到線程池中下載圖片
        submitLoadRequest(imageUrl, 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)) {
                    imageView.setImageBitmap(bitmap);
             }
                mImageCache.put(imageUrl, 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.java
public interface ImageCache {
    public Bitmap get(String url);
    public void put(String url, Bitmap bmp);
}
// 內存緩存MemoryCache類
public class MemoryCache implements ImageCache {
    private LruCache<String, Bitmap> mMemeryCache;
 
    public MemoryCache() {
        // 初始化LRU緩存
    }
 
     @Override
    public Bitmap get(String url) {
        return mMemeryCache.get(url);
    }
 
    @Override
    public void put(String url, Bitmap bmp) {
        mMemeryCache.put(url, bmp);
    }
}
 
// SD卡緩存DiskCache類
public  class  DiskCache implements ImageCache {
    @Override
    public Bitmap get(String url) {
        return null/* 從本地文件中獲取該圖片 */;
    }
 
    @Override
    public void put(String url, Bitmap bmp) {
        // 將Bitmap寫入文件中
    }
}
 
// 雙緩存DoubleCache類
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;
     }
 
    // 將圖片緩存到內存和SD卡中
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}
 

ImageLoader類中增加了一個setImageCache(ImageCache cache)函數,用戶可以通過該函數設置緩存實現,也就是通常說的依賴注入。下面就看看是如何設置緩存實現的:
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 bmp) {
            // 緩存圖片
       }
 
            @Override
        public Bitmap get(String url) {
            return null/*從緩存中獲取圖片*/;
       }
    });
 

總結:通過setImageCache(ImageCache cache)方法注入不同的緩存實現,這樣不僅能夠使ImageLoader更簡單、健壯,也使得ImageLoader的可擴展性、靈活性更高。MemoryCache、DiskCache、DoubleCache緩存圖片的具體實現完全不一樣,但是,它們的一個特點是都實現了ImageCache接口。當用戶需要自定義實現緩存策略時,只需要新建一個實現ImageCache接口的類,然後構造該類的對象,並且通過setImageCache(ImageCache cache)注入到ImageLoader中,這樣ImageLoader就實現了變化萬千的緩存策略,而擴展這些緩存策略並不會導致ImageLoader類的修改。

這就是開閉原則,雖然這些漂亮代碼不是自己寫的,但是受益匪淺,值得分享。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章