單一職責(zé)原則
- 讀《Android源碼設(shè)計模式》
- 單一職責(zé)的定義為:就一個類而言鲁冯,應(yīng)該僅有一個引起它變化的原因鹦筹,簡單來說介衔,一個類中應(yīng)該是一組相關(guān)性很高的函數(shù),數(shù)據(jù)的封裝
- 我們從最入門的方式入手
入手
- 假設(shè)現(xiàn)在要實現(xiàn)圖片加載的功能脚翘,并且能將圖片緩存灼卢,我們可能寫出的代碼是這樣的
public class ImageLoader {
//圖片緩存
LruCache<String,Bitmap> mImageCache;
//線程池,線程數(shù)量為CPU的數(shù)量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI Handler
Handler mUIHandler = new Handler(Looper.getMainLooper());
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 bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
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)){
updataImageView(imageView,bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
private void updataImageView(final ImageView imageView,final Bitmap bmp){
mUIHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bmp);
}
});
}
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
- 代碼很簡單来农,可是大概分析一下不難看出鞋真,我們的所有功能都聚合在一個類里面,當(dāng)我們的需求增多的時候沃于,所有的代碼都擠在一個類里面涩咖,這將給我們的維護帶來了很大的麻煩
- 那么怎么解決呢?
改進
- 我們提出的方法是:將ImageLoader類拆分一下繁莹,把各個功能獨立出來
- 各個功能獨立檩互?我們原本的這個ImageLoader類有什么功能?圖片加載和圖片緩存咨演?那好吧闸昨,就把圖片緩存提出來吧?我們單獨寫一個圖片緩存的類
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 bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
public void put(String url , Bitmap bitmap){
mImageCache.put(url,bitmap);
}
public Bitmap get(String url){
return mImageCache.get(url);
}
}
- 可以看到,我們只是將緩存類的put和get方法抽出去而已饵较,
- 然后看一下我們的圖片加載類怎么改的
public class ImageLoader {
//圖片緩存
ImageCache mImageCache = new ImageCache();
//線程池拍嵌,線程數(shù)量為CPU的數(shù)量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI Handler
Handler mUIHandler = new Handler(Looper.getMainLooper());
public void displayImage(final String url, final ImageView imageView){
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)){
updataImageView(imageView,bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
private void updataImageView(final ImageView imageView,final Bitmap bmp){
mUIHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bmp);
}
});
}
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;
}
}
- 具體也沒什么大的改動,就是用到了緩存類的對象去調(diào)用相關(guān)方法
- 這樣拆分之后循诉,每個類的功能很明確横辆,且代碼量也得到了減少,雖然可擴展性還是沒那么好茄猫,但是最起碼思路龄糊,代碼結(jié)構(gòu)變得清晰許多
總結(jié)
- 其實上面的改進思想就是單一職責(zé)的思想:根據(jù)不同的功能,合理的劃分一個類募疮,或者一個函數(shù)的職責(zé)炫惩,關(guān)于這個劃分倒是沒有一個特別強制的概念,每個人都對功能的劃分有自己的理解阿浓,具體項目中的代碼就需要根據(jù)個人經(jīng)驗與具體邏輯而定他嚷,
開閉原則
- 開閉原則的定義:軟件中的對象(類,模塊芭毙,函數(shù)等)應(yīng)該對于擴展是開放的筋蓖,但是對于修改是封閉的,在軟件的生命周期內(nèi)退敦,因為變化粘咖,升級和維護等原因需要對軟件原有代碼進行修改時,可能會將錯誤引入原本經(jīng)過測試的舊代碼中侈百,破壞原有系統(tǒng)瓮下,因此,當(dāng)軟件需要變化時钝域,我們應(yīng)該盡量通過擴展的方式來實現(xiàn)變化讽坏,而不是通過破壞已有的代碼來實現(xiàn)
- 當(dāng)然,一定的不改變原有代碼是不現(xiàn)實的例证,不過路呜,我們在開發(fā)過程中,應(yīng)盡量遵循這個開閉原則
入門
- 還是之前的那個例子织咧,通過使用不難發(fā)現(xiàn)胀葱,我們雖然寫的這個類具有緩存圖片的功能,但是當(dāng)程序重啟的時候我們之前的緩存都會丟掉笙蒙,因為我們的緩存全都是簡單的緩存在運行內(nèi)存中抵屿,這樣不就會影響Android系統(tǒng)的性能,(因為Android手機的運行內(nèi)存始終有限手趣,我們無法讓一個App占用手機太多運行內(nèi)存)晌该,具有易失性肥荔,重啟程序的時候又會重新下載,浪費用戶流量朝群,基于此燕耿,我們打算將緩存做成緩存在SD卡當(dāng)中
- 先寫緩存到SD卡中的類
public class DiskCache {
private static final String TAG = "DiskCache";
static String cacheDir = null;
public DiskCache() {
cacheDir = getSDPath() + "/sadsaf";
}
public String getSDPath(){
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState()
.equals(android.os.Environment.MEDIA_MOUNTED);//判斷sd卡是否存在
if(sdCardExist) {
//這里得到的是手機內(nèi)置存儲空間的根目錄
sdDir = Environment.getExternalStorageDirectory();
Log.d(TAG, "getSDPath: " + sdDir.toString());
}else {
//而這個得到的是手機外部SD卡的根目錄,但是一般Android 是不允許我們對此目錄下文件進行讀寫操作
sdDir = Environment.getDataDirectory();
Log.d(TAG, "getSDPath: " + sdDir.toString());
}
return sdDir.toString();
}
public Bitmap get(String url){
Log.d(TAG, "get: 在這里" + url);
String fileName = creatFileName(url);
return BitmapFactory.decodeFile(cacheDir + fileName);
}
public void put(String url, Bitmap bmp){
FileOutputStream fileOutputStream = null;
try{
File file = new File(cacheDir);
if(!file.exists()){
Log.d(TAG, "put: 文件夾不存在姜胖,先創(chuàng)建出文件夾");
if(!file.mkdirs()){
Log.d(TAG, "put: 文件夾創(chuàng)建失敗");
}
if (file.exists()){
Log.d(TAG, "put: 文件夾已經(jīng)存在");
}
}
String s = cacheDir + creatFileName(url);
Log.d(TAG, "put: 準備打開文件流 " + s);
fileOutputStream = new FileOutputStream(s);
bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fileOutputStream != null){
try{
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//因為我們直接獲取網(wǎng)絡(luò)圖片的話誉帅,圖片的地址會含有斜杠,而這個斜杠在寫文件的時候會被當(dāng)成文件夾目錄右莱,故此會出錯蚜锨,所以這里我將
//文件的url處理,讓他的名字取網(wǎng)絡(luò)圖片url的最后一個斜杠之后的東西
private String creatFileName(String url){
StringBuilder builder = new StringBuilder(url);
String s;
if(url.contains("/")){
int i = builder.lastIndexOf("/");
s = builder.substring(i, builder.length());
}else {
s = builder.toString();
}
return s;
}
- 那么接下來改一下我們ImageLoader慢蜓,讓他具有設(shè)置SD卡緩存的能力
-
注:這里的SD卡寫入問題亚再,以及權(quán)限問題,就不在這里細說了晨抡,如果在這里有問題的話氛悬,自行百度
- 看ImageLoader改動的內(nèi)容
//圖片 內(nèi)存 緩存
ImageCache mImageCache = new ImageCache();
//圖片SD卡或手機內(nèi)存緩存
DiskCache mDiskCache = new DiskCache();
//是否使用SD卡緩存
boolean isUseDiskCache = false;
public void displayImage(final String url, final ImageView imageView){
//判斷使用的是哪種緩存,并將緩存中的東西取出來(如果有的話)
Bitmap bitmap = isUseDiskCache ? mDiskCache.get(url) : mImageCache.get(url);
if(bitmap != null){
Log.d(TAG, "displayImage: 獲取到緩存");
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)){
updataImageView(imageView,bitmap);
}
if(isUseDiskCache){
mDiskCache.put(url,bitmap);
}else {
mImageCache.put(url,bitmap);
}
}
});
}
//是否使用SD卡緩存
public void useDiskCache(boolean useDiskCache){
isUseDiskCache = useDiskCache;
}
- 這里我們在Activity里面設(shè)置使用SD卡緩存就ok啦
String url = "http://img2.imgtn.bdimg.com/it/u=4060216298,1329589408&fm=27&gp=0.jpg";
mImageView = findViewById(R.id.main_IV);
ImageLoader loader = new ImageLoader();
loader.useDiskCache(true);//設(shè)置用SD卡緩存
loader.displayImage(url,mImageView);
- 寫成這樣如捅,我們就可以很方便的選擇緩存方式,非常方便
- 但是不知道大家思考過沒调煎?如果我想兩種緩存都使用呢镜遣?
- 如果這樣的話,用目前的代碼去達到這種要求是不是太過復(fù)雜士袄?那怎么辦悲关?
- 我們可以提供這樣一個思路,當(dāng)要獲取圖片的時候窖剑,我們先看看內(nèi)存緩存里面有沒有坚洽,如果沒有戈稿,再看看SD卡緩存里面有沒有西土,如果都沒有,再去網(wǎng)絡(luò)上獲取是不是更加人性化一些呢?
繼續(xù)探索
- 這里有兩種方案鞍盗,一種是我們直接在原來代碼上面改需了,一種是創(chuàng)建一個新的可以實現(xiàn)同時兩種緩存都支持的類
- 那么想想看?我們剛才說的開閉原則般甲?好吧肋乍,直接選擇第二種方案
- 來看看我們這個雙緩存類(DoubleCache)的實現(xiàn)
public class DoubleCache {
ImageCache mMemoryCache = new ImageCache();
DiskCache mDiskCache = new DiskCache();
//先從內(nèi)存緩存中獲取,如果沒有敷存,再從SD中獲取
public Bitmap get(String url){
Bitmap bitmap = mMemoryCache.get(url);
if(bitmap == null){
bitmap = mDiskCache.get(url);
}
return bitmap;
}
//把圖片緩存到內(nèi)存和SD中
public void put(String url,Bitmap bitmap){
mMemoryCache.put(url,bitmap);
mDiskCache.put(url,bitmap);
}
}
- 代碼沒有任何難度墓造,當(dāng)提供了雙緩存機制之后堪伍,我們就可以去修改下我們的加載類了(ImageLoader)
//雙緩存
DoubleCache mDoubleCache = new DoubleCache();
//是否使用雙緩存
boolean isUseDoubleCache = false;
public void displayImage(final String url, final ImageView imageView){
//判斷使用的是哪種緩存,并將緩存中的東西取出來(如果有的話)
Bitmap bitmap;
if(isUseDoubleCache){
bitmap = mDoubleCache.get(url);
}else if(isUseDiskCache){
bitmap = mDiskCache.get(url);
}else {
bitmap = mImageCache.get(url);
}
if(bitmap != null){
Log.d(TAG, "displayImage: 獲取到緩存");
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)){
updataImageView(imageView,bitmap);
}
if(isUseDiskCache){
mDiskCache.put(url,bitmap);
}else {
mImageCache.put(url,bitmap);
}
}
});
}
//是否使用雙緩存
public void UseDoubleCache(boolean useDoubleCache) {
isUseDoubleCache = useDoubleCache;
}
- 貌似?蠻好的帝雇?好像是符合開閉原則來著?
- 來蛉拙,回過頭想一下尸闸,我們剛才為了添加雙緩存機制,修改了幾個類的代碼孕锄?好像基本上都修改了吧
-
問題:每次加入新的緩存方法都要修改原來的代碼吮廉,這樣可能引來新的bug,而且畸肆,照這樣的實現(xiàn)方法宦芦,用戶是不能自己實現(xiàn)自定義緩存實現(xiàn)的
- 這里基于這個問題,我們再來看一下開閉原則的定義:軟件中的對象(類轴脐,模塊踪旷,函數(shù)等)應(yīng)該對于擴展是開放的,但是對于修改是封閉的豁辉,也就是說令野,當(dāng)軟件需要變化時,我們應(yīng)該盡量通過擴展的方式來實現(xiàn)變化徽级,而不是通修改已有的代碼來實現(xiàn)
- 如果要實現(xiàn)用戶自定義緩存機制實現(xiàn)的話气破,我們是不是應(yīng)該抽出一個緩存接口?
public interface IImageCache {
public Bitmap get(String url);
public void put(String url,Bitmap bitmap);
}
- 然后讓我們之前寫的三個緩存類實現(xiàn)這個接口餐抢,這里就不貼代碼现使,接下來我們?nèi)タ纯磮D片加載類怎么做的(ImageLoader)
public class ImageLoader {
private final static String TAG = "ImageLoader";
//默認為內(nèi)存緩存
IImageCache mImageCache = new MemeryCache();
//線程池,線程數(shù)量為CPU的數(shù)量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI Handler
Handler mUIHandler = new Handler(Looper.getMainLooper());
//外部注入緩存
public void setImageCache(IImageCache mImageCache){
this.mImageCache = mImageCache;
}
public void displayImage(final String url, final ImageView imageView){
//直接來獲取緩存
Bitmap bitmap = mImageCache .get(url);
if(bitmap != null){
Log.d(TAG, "displayImage: 獲取到緩存");
imageView.setImageBitmap(bitmap);
return;
}
//如果沒有緩存旷痕,就去線程池中請求下載網(wǎng)絡(luò)圖片
submitLoadRequest(url,imageView);
}
private void submitLoadRequest(final String url, final ImageView imageView) {
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if(bitmap == null){
Log.d(TAG, "run: 網(wǎng)絡(luò)圖片下載失敗");
return;
}
if (imageView.getTag().equals(url)){
updataImageView(imageView,bitmap);
}
//設(shè)置緩存
mImageCache.put(url,bitmap);
}
});
}
//更新UI
private void updataImageView(final ImageView imageView,final Bitmap bmp){
mUIHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bmp);
}
});
}
//下載網(wǎng)絡(luò)圖片
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;
}
}
- 從這個ImageLoader里面我們可以看出來碳锈,這個類已經(jīng)相當(dāng)成熟了,里面的東西我們基本不需要再去改變欺抗,想用哪種緩存售碳,哪怕是我們自己的緩存方式,只要我們實現(xiàn)了那個接口绞呈,然后調(diào)用set方法將我們的緩存注入進去即可贸人,
- 現(xiàn)在想想看?如果我們現(xiàn)在需要使用一種新的緩存方式該怎么做呢佃声?只需實現(xiàn)我們自己的緩存邏輯艺智,然后在調(diào)用一下set方法,即可完美使用圾亏,如下:
String url = "http://img2.imgtn.bdimg.com/it/u=4060216298,1329589408&fm=27&gp=0.jpg";
mImageView = findViewById(R.id.main_IV);
ImageLoader loader = new ImageLoader();
DoubleCache doubleCache = new DoubleCache();
loader.setImageCache(doubleCache);
loader.displayImage(url,mImageView);
- 這就是開閉原則十拣,我們的圖片加載機制對于擴展式開放的封拧,我們可以任意去擴展我們的緩存機制,而不用去管一點點圖片加載的細節(jié)夭问,就可以實現(xiàn)代碼的開閉原則
- 這就是六大原則的前兩種哮缺,預(yù)知后面如何,且聽下回分解