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è)類 ImageLoader
和 ImageCache
,ImageLoader
只負(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
接口的類,再通過 ImageLoader
的 setImageCache
方法注入即可。
/**
* 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):
- 高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象
- 抽象不應(yīng)該依賴細(xì)節(jié)
- 細(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é)不需要租戶知道太多,所有的事情通過與中介溝通。