面向對象六大原則(二):開閉原則

一、概述

開閉原則(Open Close Principle,縮寫:OCP),軟件中的對象(類、模塊、函數等)應該對於擴展是“開放”的,但是對於修改是“封閉”的。通俗點講就是軟件系統中包含的各種組件應該在不修改現有代碼的基礎上引入新功能。“開”是對於組件功能的擴展是開放的,是允許對其進行功能擴展的;“閉”是對於原有代碼的修改是封閉的,即不應該修改原有代碼。

二、特徵

讓程序更穩定、更靈活:

1.對於擴展是開放的:當應用的需求改變時,我們可以對模塊進行擴展,使其具有滿足那些改變的新行爲。也就是說,我們可以改變模塊的功能。
2.對於修改是封閉的:對模塊行爲進行擴展時,不必改動模塊的源代碼或者二進制碼。

三、實現方法

實現開閉原則的關鍵在於“抽象”。把系統的所有可能的行爲抽象成一個抽象底層,這個抽象底層規定出所有的具體實現必須提供的方法的特徵。下面引用《Android源碼設計模式解析與實戰》一書中圖片加載器(ImageLoader)的代碼例子(是在我上一篇博客“面向對象六大原則(一):單一職責原則”代碼例子根據開閉原則進行新功能的添加及優化):

1.圖片緩存接口,用來抽象圖片緩存的功能:

public interface ImageCache {
    public Bitmap get(String url);
    public void put(String url, Bitmap bmp);
}

2.ImageCache接口簡單定義了獲取、緩存圖片兩個函數,緩存的key是圖片的url,值是圖片本身。內存緩存、SD卡緩存、雙緩存都實現了該接口,以下是緩存類的實現:

/**
 * 內存緩存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卡中獲取
    @Override
    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if(bitmap == null){
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    //將圖片緩存到內存和SD卡中
    @Override
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}


3.ImageLoader主程序代碼重構

/**
 * 圖片加載類
 */
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(final String imageUrl, final 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;
    }
}

4.用戶可通過setImageCache(ImageCache cache)方法注入不同的緩存實現,不僅能使ImageLoader更簡單、健壯,也使得ImageLoader的可擴展性、靈活性更高。

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;/*從緩存中獲取圖片*/
        }
    });

通過以上 代碼 可知MemoryCache、DiskCache、DoubleCache緩存圖片的具體實現完全不一樣,但是都實現了ImageCache接口。當用戶需要自定義實現緩存策略時,只需要新建一個實現ImageCache接口的類,然後構造該類的對象,並且通過setImageCache(ImageCache cache)注入到ImageLoader中,這樣ImageLoader就實現了千變萬化的緩存策略,且擴展這些緩存策略並不會導致ImageLoader類的修改。

四、分析
         
開閉原則指導我們,當軟件需要變化時,應該儘量通過擴展的方式來實現變化,而不是通過修改已有的代碼來實現。這裏強調“應該儘量”是說明OCP原則並不是說絕對不可以修改原始類的。

當我們嗅到原來的代碼“腐化氣味”時,應該儘早的重構,以便使代碼恢復到正常的“進化”過程,而不是通過繼承等方式添加新的實現,這會導致類型的膨脹以及歷史遺留代碼的冗餘。

我們的開發過程並不是理想化的(達到完全不用修改原來的代碼),要結合實際情況進行考量,看是否通過修改舊代碼或者是繼承使得軟件系統更穩定、更靈活,在保證去除“代碼腐化”的同時,也保證原有模塊的正確性。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章