單一職責的英文名稱是Single Responsibility Principle址遇,縮寫是SRP熄阻。
SRP的定義就是:就一個類而言,應(yīng)該僅有一個引起它變化的原因倔约。簡單說來秃殉,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)、數(shù)據(jù)的封裝浸剩。單一職責的劃分界限并不是總是那么清晰钾军,很多時候需要靠個人經(jīng)驗來界定。當然绢要,最大的問題就是堆職責的定義吏恭,什么是類的職責,以及怎么劃分類的職責重罪。
接下來以小明的工作過程為示例描述單一職責原則:
小明初入職場砸泛,在經(jīng)歷過一周的適應(yīng)期以及熟悉公司的產(chǎn)品、開發(fā)規(guī)范后蛆封,小民的開發(fā)工作就正式開始了唇礁。小民的主管是個工作經(jīng)驗豐富的技術(shù)專家,對于小民的工作并不是很滿意惨篱,尤其是小民最薄弱的面向?qū)ο笤O(shè)計盏筐,而Android開發(fā)又是使用Java語言,程序中的抽象砸讳、接口琢融、六大原則、23種設(shè)計模式等名詞把小民弄的暈頭轉(zhuǎn)向簿寂。于是漾抬,小民的主管決定先讓小民做一個小項目來鍛煉這方面的能力。
在經(jīng)過一番思考后常遂,主管挑選了使用范圍廣纳令、難度也適中的圖片加載器(ImageLoader)作為小民的訓(xùn)練項目。既然要訓(xùn)練小民的面向?qū)ο笤O(shè)計能力,那么久必須考慮到可擴展性平绩、靈活性圈匆,而檢測這一切是否符合需求的最好途徑就是開源。
小民是不服輸?shù)哪蟠疲鞴艿囊蠛芎唵卧咀∶駥崿F(xiàn)圖片加載,并且要將圖片緩存起來性湿。在分析了需求之后纬傲,小民放下心來,胸有成竹肤频,在經(jīng)歷了10分鐘的編碼之后叹括,小民寫下了如下代碼:
ImageLoader.java
package com.deason.library.srp;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.widget.ImageView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 圖片加載類
* Created by liuguoquan on 2016/3/13.
*/
public class ImageLoader {
/**
* 圖片緩存
*/
LruCache<String, Bitmap> mImageCache;
/**
* 線程池,線程數(shù)量未CPU的數(shù)量
*/
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
public ImageLoader() {
initImageCache();
}
private void initImageCache() {
//計算可使用的最大內(nèi)存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取內(nèi)存的四分之一作為緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 顯示圖片
* @param url
* @param imageView
*/
public void displayImage(final String url, final ImageView imageView) {
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);
}
}
});
}
/**
* 下載圖片
* @param url
* @return
*/
public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection mConnection = (HttpURLConnection) url.openConnection();
int code = mConnection.getResponseCode();
if (200 == code) {
bitmap = BitmapFactory.decodeStream(mConnection.getInputStream());
}
mConnection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
隨后着裹,小民將代碼托管到GitHub上,伴隨著git push命令的完成米同,小民的ImageLoader 0.1版本就發(fā)布了骇扇。小民開始幻想著待會兒被主管稱贊。
在小民給主管報告了ImageLoader發(fā)布消息的幾分鐘后面粮,主管把小民叫到了會議室少孝,叼了小民一頓:“小民,你的ImageLoader耦合太嚴重啦熬苍!簡直就沒有設(shè)計可言稍走,更不要說是擴展性、靈活性了柴底。所有功能寫在一個類里面怎么行呢婿脸,這樣隨著功能的增多,ImageLoader類會越來越大柄驻。代碼也越來越復(fù)雜狐树,圖片的加載系統(tǒng)就會越來越弱...”,此時鸿脓,小民默默吞下了淚水抑钟。
主管最后說:“你還是把ImageLoader拆分一下,把各個功能獨立出來野哭,讓它們滿足單一職責原則在塔。”小民敏銳的捕捉到單一職責原則這個關(guān)鍵詞拨黔,他用百度蛔溃、Google搜索了資料之后,決定對ImageLoader進行一次重構(gòu)。這次小民認真地先畫了一幅UML類圖:
ImageLoader修改后的代碼如下:
package com.deason.library.srp.refactor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 圖片加載類
* Created by liuguoquan on 2016/3/14.
*/
public class ImageLoader {
/**
* 圖片緩存
*/
ImageCache mImageCache = new ImageCache();
/**
* 線程池城榛,線程數(shù)量未CPU的數(shù)量
*/
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
/**
* 顯示圖片
* @param url
* @param imageView
*/
public void displayImage(final String url, final ImageView imageView) {
//首先檢查內(nèi)存是否存在
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);
}
//將下載的圖片存入內(nèi)存
mImageCache.put(url,bitmap);
}
});
}
/**
* 下載圖片
* @param url
* @return
*/
public Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection mConnection = (HttpURLConnection) url.openConnection();
int code = mConnection.getResponseCode();
if (200 == code) {
bitmap = BitmapFactory.decodeStream(mConnection.getInputStream());
}
mConnection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
并且添加類一個ImageCache類用于圖片緩存揪利,具體代碼如下:
package com.deason.library.srp.refactor;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* 處理圖片緩存
* Created by liuguoquan on 2016/3/14.
*/
public class ImageCache {
/**
* 圖片緩存
*/
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
private void initImageCache() {
//計算可使用的最大內(nèi)存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取內(nèi)存的四分之一作為緩存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 將圖片存入緩存
* @param key
* @param bitmap
*/
public void put(String key,Bitmap bitmap) {
mImageCache.put(key,bitmap);
}
/**
* 取出緩存圖片
* @param key
* @return
*/
public Bitmap get(String key) {
return mImageCache.get(key);
}
}
綜上所述,小民將ImageLoader一拆為二狠持,ImageLoader只負責圖片加載的邏輯疟位,而ImageCache只負責處理圖片緩存的邏輯,這樣ImageLoader的代碼量變少了喘垂,職責也清晰了甜刻;當與緩存相關(guān)的邏輯需要改變時,不需要修改ImageLoader類正勒,而圖片加載的邏輯需要修改時也不會影響到緩存處理邏輯得院。
從上述例子我們知道,單一職責所表達出來的用意就是“單一”兩個字章贞。如何劃分一個類祥绞、一個函數(shù)的職責,每個人都有自己的看法鸭限,這需要根據(jù)個人經(jīng)驗蜕径、具體的業(yè)務(wù)邏輯而定。但是败京,它也有一些基本的指導(dǎo)原則兜喻,例如,兩個完全不一樣的功能就不應(yīng)該方法一個類中赡麦,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)朴皆、數(shù)據(jù)的封裝。工程師可以不斷地審視自己的代碼泛粹,根據(jù)具體的業(yè)務(wù)遂铡、功能對類進行相應(yīng)的拆分,這是程序員優(yōu)化代碼邁出的第一步晶姊。
優(yōu)點
- 類的復(fù)雜度降低忧便,實現(xiàn)什么職責都有清晰的定義。
- 復(fù)雜性降低帽借,所以可讀性提高了珠增。
- 可讀性提高了,所以可維護性提高了
- 變更引起的風險降低砍艾,變更是必不可少的蒂教,如果接口的單一職責做得好,一個接口修改只對相應(yīng)的實現(xiàn)類有影響脆荷,對其他的接口無影響凝垛,這對系統(tǒng)的擴展性懊悯、維護性都有很大的幫助。
參考資料
《Android源碼設(shè)計模式》 何紅輝梦皮、關(guān)愛民 著