設(shè)計(jì)模式讀書筆記(一)面向?qū)ο罅笤瓌t

1. 單一職責(zé)原則(Single Responsibility Principle)

對(duì)一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因,……,一個(gè)類應(yīng)該是一組相關(guān)性很高的函數(shù)、數(shù)據(jù)的封裝。……。這是一個(gè)備受爭議卻有極其重要的原則,……,需要靠個(gè)人的經(jīng)驗(yàn)來界定。

1.1. 違反單一職責(zé)原則的例子

public class ImageLoader {
    // 圖片緩存
    LruCache<String, Bitmap> mImageCache;

    public ImageLoader() {
        initImageCache();
    }

    /**
     * 初始化圖片緩存
     */
    private void initImageCache() {
        // ……
    }

    /**
     * 顯示圖片
     * @param url 圖片地址
     * @param imageView ImageView
     */
    public void displayImage(final String url, final ImageView imageView) {
        // ……
    }

    /**
     * 下載圖片
     * @param imageUrl 圖片地址
     * @return 圖片 Bitmap
     */
    public Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        
        // ……
        
        return bitmap;
    }
}

這是一個(gè)簡單的 ImageLoader 實(shí)現(xiàn),其中包含了緩存相關(guān)的邏輯,根據(jù)單一職責(zé)原則的要求,這是不合理的,應(yīng)該講緩存初始化等邏輯獨(dú)立至一個(gè)圖片緩存類中,在 ImageLoader 中,只保留與圖片加載相關(guān)的邏輯代碼。

1.2. 修改之后的代碼

/**
 * 圖片加載類
 */
public class ImageLoader {
    // 圖片緩存
    ImageCache mImageCache = new ImageCache();

    public ImageLoader() {
        
    }

    /**
     * 顯示圖片
     * @param url 圖片地址
     * @param imageView ImageView
     */
    public void displayImage(final String url, final ImageView imageView) {
        // ……
    }

    /**
     * 下載圖片
     * @param imageUrl 圖片地址
     * @return 圖片 Bitmap
     */
    public Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;

        // ……

        return bitmap;
    }
}
/**
 * 圖片緩存類
 */
public class ImageCache {
    // LRU 緩存
    LruCache<String, Bitmap> mImageCache;

    public ImageCache() {
        initImageCache();
    }
    
    /**
     * 初始化圖片緩存
     */
    private void initImageCache() {
        // ……
    }

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap);
    }

    public Bitmap get(String url) {
        return mImageCache.get(url);
    }
}

修改后的代碼將上面例子中的類拆分為了兩個(gè)類 ImageLoaderImageCacheImageLoader 只負(fù)責(zé)圖片加載相關(guān)的邏輯,ImageCache 則負(fù)責(zé)圖片緩存,使得類職責(zé)分明。

2. 開閉原則(Open Close Principle)

軟件中的對(duì)象(類、模塊、函數(shù)等)應(yīng)該對(duì)擴(kuò)展是開放的,但是,對(duì)于修改是封閉的。……。當(dāng)軟件需要變化時(shí),我們應(yīng)該盡量通過擴(kuò)展的方式來實(shí)現(xiàn)變化,而不是通過修改已有的代碼來實(shí)現(xiàn)。

2.1. 違反開閉原則的例子

繼續(xù)拿上面的 ImageLoader 舉例,根據(jù)上述的代碼,我們目前只進(jìn)行了內(nèi)存緩存,如果我們需要添加一種緩存方式,支持緩存至 SD 卡中,那我們就需要添加一個(gè) DiskCache 類,實(shí)現(xiàn) SD 卡緩存邏輯,并且修改 ImageLoader 的代碼,將其中的 ImageCache 替換為 DiskCache,如果兩個(gè)緩存類提供的方法不同的話,我們還需要去修改方法調(diào)用處,這就違反了開閉原則,對(duì)于這種后期的需求更改,我們不應(yīng)該通過修改已有代碼達(dá)到目的,而是應(yīng)該在不修改原有代碼的情況下進(jìn)行擴(kuò)展,而在現(xiàn)有的代碼結(jié)構(gòu)下,我們無法做到不修改原有代碼來實(shí)現(xiàn)新需求。

2.2. 修改代碼使其符合開閉原則

public interface ImageCache {
    Bitmap get(String url);

    void put(String url, Bitmap bmp);
}
public class MemoryCache implements ImageCache {
    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCache() {

    }

    @Override
    public Bitmap get(String url) {
        return mMemoryCache.get(url);
    }

    @Override
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
    }
}
public class ImageLoader {
    /** 圖片緩存 */
    private ImageCache mImageCache = new MemoryCache();
    /** 線程池,線程數(shù)量為 CPU 的數(shù)量 */
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void setImageCache(ImageCache imageCache) {
        mImageCache = imageCache;
    }

    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        // 圖片沒有緩存,提交到線程池中進(jìn)行下載
        submitLoadRequest(imageUrl, imageView);
    }

    private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
        imageView.setTag(imageUrl);
        mExecutorService.execute(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);
            }
        });
    }

    private Bitmap downloadImage(String imageUrl) {
        Bitmap bitmap = null;
        try{
            URL url = new URL(imageUrl);
            final URLConnection conn = url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

在修改代碼之后,我們主要關(guān)注 ImageLoader 中的 mImageCache 屬性,它是一個(gè) ImageCache 引用,而 ImageCache 是一個(gè) Interface,引入接口是實(shí)現(xiàn)開閉原則的關(guān)鍵之一,根據(jù)多態(tài)的特性,mImageCache 可以指向任何一個(gè)實(shí)現(xiàn)了 ImageCache 的類的對(duì)象,這里指向了一個(gè) MemoryCache 對(duì)象。在 ImageLoader 中同時(shí)提供了一個(gè) setter 方法 setImageCache可以通過該方法來注入 ImageCache,當(dāng)我們完成這兩點(diǎn),ImageLoader 類也就符合了開閉原則,當(dāng)我們需要實(shí)現(xiàn)上述的需求,即更換緩存方式為緩存至 SD 卡或者同時(shí)使用兩種緩存方式時(shí),我們不需要對(duì) ImageLoader 類做任何修改,僅僅需要?jiǎng)?chuàng)建一個(gè)新的實(shí)現(xiàn)了 ImageCache 接口的類,再通過 ImageLoadersetImageCache 方法注入即可。

/**
 * SD 卡緩存類,實(shí)現(xiàn)了 ImageCache 接口
 */
public class DiskCache implements ImageCache {
    @Override
    public Bitmap get(String url) {
        return null;  // TODO: 2017/10/25 從本地文件中獲取圖片
    }

    @Override
    public void put(String url, Bitmap bmp) {
        // TODO: 2017/10/25 將 Bitmap 寫入文件
    }
}
/**
 * 雙緩存類,同樣實(shí)現(xiàn)了 ImageCache 接口
 */
public class DoubleCache implements ImageCache {
    private ImageCache mMemoryCache = new MemoryCache();

    private ImageCache mDiskCache = new DiskCache();

    @Override
    public Bitmap get(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    @Override
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}
// 換用不同緩存方式的方法
imageLoader.setImageCache(new DiskCache());
imageLoader.setImageCache(new DoubleCache());
或者這樣
imageLoader.setImageCache(new ImageCache() {
    @Override
    public Bitmap get(String url) {
        return null;
    }

    @Override
    public void put(String url, Bitmap bmp) {

    }
});

3. 里氏替換原則(Liskov Substitution Principle)

所有引用基類的地方必須能透明地使用其子類的對(duì)象。……。開閉原則和里氏替換原則往往是生死相依、不棄不離的,通過里氏替換達(dá)到對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉的效果。這兩個(gè)原則都同時(shí)強(qiáng)調(diào)了一個(gè) OOP 的重要特性——封裝。

4. 依賴倒置原則(Dependence Inversion Principle)

依賴倒置原則指代了一種特定的解耦方式,使得高層次的模塊不依賴于低層次的模塊的實(shí)現(xiàn)細(xì)節(jié)。

有以下幾個(gè)關(guān)鍵點(diǎn):

  1. 高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象
  2. 抽象不應(yīng)該依賴細(xì)節(jié)
  3. 細(xì)節(jié)應(yīng)該依賴抽象

模塊間到的依賴通過抽象發(fā)生,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的。
面向接口編程或者說是面向抽象編程,也就是上兩個(gè)原則中說到的抽象。

依賴倒置原則說的其實(shí)就是上面提到的 ImageLoader 不應(yīng)該去依賴 ImageCache 的具體實(shí)現(xiàn)類,比如 MemoryCache 或者 DiskCache,而應(yīng)該是通過接口或抽象來發(fā)生依賴關(guān)系,也就是后來加的 ImageCache 接口。

5. 接口隔離原則(Interface Segregation Principles)

客戶端不應(yīng)該依賴它不需要的接口。另一種定義是:類間的依賴關(guān)系應(yīng)該建立在最小的接口上。接口隔離原則將非常龐大、臃腫的接口拆分成更小和更具體的接口,這樣客戶將會(huì)只需要知道他們感興趣的接口。

接口隔離原則的含義其實(shí)就是將接口拆分的盡可能小,這樣以便于重構(gòu)。書中以 JDK 中的 Closable 接口舉例,這個(gè)接口僅定義了一個(gè) close 方法,所有 Closable 接口的實(shí)現(xiàn)類,例如 FileOutputStream 在調(diào)用 close 方法時(shí)都需要 catch IOException,于是書中寫了一個(gè)工具類來解決這一問題。工具類代碼如下:

public class CloseUtils {
    private CloseUtils() {}

    /**
     * 關(guān)閉 Closable 對(duì)象
     * @param closeable
     */
    public static void closeQuietly(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

講這個(gè)的目的其實(shí)是更好的幫助我們理解接口隔離原則,當(dāng)把接口變得更小更具體之后,系統(tǒng)就有了更高的靈活性。

6. 迪米特原則(Law of Demeter,也稱為最少知識(shí)原則 Least Knowledge Principle)

一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解。通俗的講,一個(gè)類應(yīng)該對(duì)自己需要耦合或調(diào)用的類知道的最少,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒關(guān)系,只需要知道它需要的方法即可。

其實(shí)迪米特原則說的和上面的原則也有點(diǎn)類似,首先就是要依賴抽象,作為依賴者,不需要知道方法是如何實(shí)現(xiàn)的,只需要知道依賴的對(duì)象有這個(gè)方法就好了,實(shí)現(xiàn)上的改變對(duì)它來說其實(shí)是不可見的。另一點(diǎn)就是盡可能減少依賴,書中以通過中介租房舉例,作為租戶,只需要依賴于中介即可,至于房東的房產(chǎn)證是不是真的這些細(xì)節(jié)不需要租戶知道太多,所有的事情通過與中介溝通。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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