開放 — 封閉原則

在軟件設計模式中滚局,這種不能修改工猜,但可以擴展的思想是重要的一種設計原則,是開放—封閉原則(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 類的修改枣氧。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市垮刹,隨后出現(xiàn)的幾起案子达吞,更是在濱河造成了極大的恐慌,老刑警劉巖荒典,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酪劫,死亡現(xiàn)場離奇詭異,居然都是意外死亡寺董,警方通過查閱死者的電腦和手機覆糟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遮咖,“玉大人滩字,你說我怎么就攤上這事∮蹋” “怎么了踢械?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長魄藕。 經(jīng)常有香客問我,道長撵术,這世上最難降的妖魔是什么背率? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上寝姿,老公的妹妹穿的比我還像新娘交排。我一直安慰自己,他們只是感情好饵筑,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布埃篓。 她就那樣靜靜地躺著,像睡著了一般根资。 火紅的嫁衣襯著肌膚如雪架专。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天玄帕,我揣著相機與錄音部脚,去河邊找鬼。 笑死裤纹,一個胖子當著我的面吹牛委刘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹰椒,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锡移,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了漆际?” 一聲冷哼從身側響起淆珊,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灿椅,沒想到半個月后套蒂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡茫蛹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年操刀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婴洼。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡骨坑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柬采,到底是詐尸還是另有隱情欢唾,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布粉捻,位于F島的核電站礁遣,受9級特大地震影響,放射性物質發(fā)生泄漏肩刃。R本人自食惡果不足惜祟霍,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一杏头、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沸呐,春花似錦醇王、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呼渣,卻和暖如春棘伴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徙邻。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工排嫌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缰犁。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓淳地,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帅容。 傳聞我的和親對象是個殘疾皇子颇象,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容