最近在學(xué)習(xí)設(shè)計(jì)模式, 搜索大量的資料發(fā)現(xiàn)很多資料都是只是說明這些設(shè)計(jì)模式是怎樣的, 而沒有說明實(shí)際用途, 大量的資料都是重疊重復(fù)的. 雖說入門, 但是給出例子之后就沒有再深入下去了. 學(xué)Android開發(fā)的, 很多時(shí)候看完用Java寫的設(shè)計(jì)模式代碼, 但是卻不知道怎么應(yīng)用到實(shí)際的項(xiàng)目開發(fā)中去. 所以打算根據(jù)自己的?一些拙見, 能把經(jīng)常使用的設(shè)計(jì)方法寫成文章互相交流.
從ImageLoader說起
如果需要寫一個(gè)ImageLoader,那么一般代碼就像下面那樣
public class ImageLoader {
private static final int SHOW_IMAGE = 100;
private static final ImageLoader mInstance = new ImageLoader();
private ExecutorService mExecutors;
private LruCache<String, Bitmap> mCache;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == SHOW_IMAGE) {
ImageHolder holder = (ImageHolder) msg.obj;
if (holder != null && holder.imageView != null && holder.bitmap != null && holder.url != null
&& holder.url.equals(holder.imageView.getTag())) {
holder.imageView.setImageBitmap(holder.bitmap);
}
}
};
};
/**
* 私有化構(gòu)造函數(shù)并初始化
*/
private ImageLoader() {
mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 圖片顯示
*
* @param imageView
* @param url
*/
public void displayImage(final ImageView imageView, final String url) {
imageView.setTag(url);
Bitmap cacheBitmap = mCache.get(url);
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
return;
}
final ImageHolder holder = new ImageHolder();
holder.imageView = imageView;
holder.url = url;
mExecutors.submit(new Runnable() {
@Override
public void run() {
Bitmap remoteBitmap = downloadImage(holder.url);
mCache.put(holder.url, remoteBitmap);
holder.bitmap = remoteBitmap;
Message message = mHandler.obtainMessage(SHOW_IMAGE);
message.obj = holder;
mHandler.sendMessage(message);
}
});
}
/**
* 下載圖片
*
* @param url
* @return
*/
private Bitmap downloadImage(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(new URL(url).openStream());
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 獲取實(shí)例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
/**
* 顯示圖片消息數(shù)據(jù)傳輸對象
*
* @author August
*
*/
static final class ImageHolder {
ImageView imageView;
Bitmap bitmap;
String url;
}
}
好, 至此一個(gè)ImageLoader就已經(jīng)寫完了.我們下面根據(jù)面向?qū)ο蟮牧笤瓌t對它進(jìn)行改造
單一職責(zé)原則
書面語就兩句重要的話:
就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因
一個(gè)類中應(yīng)該是一組相關(guān)性很高的函數(shù)和數(shù)據(jù)的封裝
上面我們把ImageLoader的各部分都卸載一個(gè)類中, 所以已經(jīng)違背了該原則, 我們應(yīng)該盡量地去簡化每個(gè)類的工作量. 把相關(guān)度不高的部分分離出去. 于是我們就有了下面的改造.
Downloader
/**
* 下載網(wǎng)絡(luò)圖片
*
* @author August
*
*/
public class Downloader {
public Bitmap downloadImage(String url) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(new URL(url).openStream());
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
ImageCache
/**
* 圖片緩存類
* @author August
*
*/
public class ImageCache {
private LruCache<String, Bitmap> mCache;
public ImageCache() {
mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
public Bitmap get(String url) {
return mCache.get(url);
}
public void put(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
ImageHolder
/**
* 顯示圖片消息的數(shù)據(jù)傳輸對象
*
* @author August
*
*/
public class ImageHolder {
public ImageView imageView;
public Bitmap bitmap;
public String url;
public boolean isVerify() {
return imageView != null && bitmap != null && url != null && url.equals(imageView.getTag());
}
public void showImage() {
if (isVerify()) {
imageView.setImageBitmap(bitmap);
}
}
}
ImageHandler
/**
* 消息處理類
* @author August
*
*/
public class ImageHandler extends Handler {
public static final int SHOW_IMAGE = 100;
public void handleMessage(android.os.Message msg) {
if (msg.what == SHOW_IMAGE) {
ImageHolder holder = (ImageHolder) msg.obj;
if (holder != null) {
holder.showImage();
}
}
};
}
ImageLoader
public class ImageLoader {
private static final ImageLoader mInstance = new ImageLoader();
private ExecutorService mExecutors;
private ImageCache mCache = new ImageCache();
private Downloader mDownloader = new Downloader();
private Handler mHandler = new ImageHandler();
/**
* 私有化構(gòu)造函數(shù)并初始化
*/
private ImageLoader() {
mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/**
* 圖片顯示
*
* @param imageView
* @param url
*/
public void displayImage(final ImageView imageView, final String url) {
imageView.setTag(url);
Bitmap cacheBitmap = mCache.get(url);
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
return;
}
final ImageHolder holder = new ImageHolder();
holder.imageView = imageView;
holder.url = url;
mExecutors.submit(new Runnable() {
@Override
public void run() {
Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
mCache.put(holder.url, remoteBitmap);
holder.bitmap = remoteBitmap;
Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
message.obj = holder;
mHandler.sendMessage(message);
}
});
}
/**
* 獲取實(shí)例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
可以看到現(xiàn)在我們的ImageLoader已經(jīng)是有模有樣地分開了幾個(gè)類.
開閉原則
也是兩句話作總結(jié)
軟件中的對象(類 模塊 函數(shù)等)應(yīng)該對于擴(kuò)展是開放的, 但是對于修改是封閉的.
程序一旦開發(fā)完成, 程序中的一個(gè)類的實(shí)現(xiàn)只應(yīng)該因錯(cuò)誤而被修改, 新的或者改變的特性應(yīng)該通過新建不同的類實(shí)現(xiàn), 新建的類可以通過集成的方式來重用原來的代碼.
上面的ImageLoader只在內(nèi)存中緩存, 后來發(fā)現(xiàn)一級的緩存是行不通的. 因?yàn)锽itmap占用的內(nèi)存太, 很容易被回收. 所以我們需要使用磁盤緩存. 然后我們增加DiskCache類并且修改ImageLoader.
DiskCache
public class DiskCache {
private File mDir;
public DiskCache() {
mDir = new File(Environment.getExternalStorageDirectory(), "cache");
if (!mDir.exists()) {
mDir.mkdir();
}
}
public void put(String url, Bitmap bitmap) {
OutputStream os = null;
try {
os = new FileOutputStream(new File(mDir, getDiskName(url)));
bitmap.compress(CompressFormat.JPEG, 100, os);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
bitmap.compress(CompressFormat.JPEG, 100, os);
}
public Bitmap get(String url) {
File file = new File(mDir, getDiskName(url));
Bitmap bitmap = null;
if (file.exists()) {
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
}
return bitmap;
}
private String getDiskName(String url) {
return Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
}
}
ImageLoader
public class ImageLoader {
private static final ImageLoader mInstance = new ImageLoader();
private ExecutorService mExecutors;
private DiskCache mCache = new DiskCache();
private Downloader mDownloader = new Downloader();
private Handler mHandler = new ImageHandler();
/**
* 私有化構(gòu)造函數(shù)并初始化
*/
private ImageLoader() {
mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/**
* 圖片顯示
*
* @param imageView
* @param url
*/
public void displayImage(final ImageView imageView, final String url) {
imageView.setTag(url);
Bitmap cacheBitmap = mCache.get(url);
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
return;
}
final ImageHolder holder = new ImageHolder();
holder.imageView = imageView;
holder.url = url;
mExecutors.submit(new Runnable() {
@Override
public void run() {
Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
mCache.put(holder.url, remoteBitmap);
holder.bitmap = remoteBitmap;
Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
message.obj = holder;
mHandler.sendMessage(message);
}
});
}
/**
* 獲取實(shí)例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
什么??? 只有磁盤緩存又不夠快??? 要做二級緩存???? 代碼又要改...也不怎么難, 我們添加一個(gè)DoubleCache的類
DoubleCache
/**
* 二級緩存類
*
* @author August
*
*/
public class DoubleCache {
private ImageCache mImageCache = new ImageCache();
private DiskCache mDiskCache = new DiskCache();
public Bitmap get(String url) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}
ImageLoader
public class ImageLoader {
private static final ImageLoader mInstance = new ImageLoader();
private ExecutorService mExecutors;
private DoubleCache mCache = new DoubleCache();
private Downloader mDownloader = new Downloader();
private Handler mHandler = new ImageHandler();
/**
* 私有化構(gòu)造函數(shù)并初始化
*/
private ImageLoader() {
mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/**
* 圖片顯示
*
* @param imageView
* @param url
*/
public void displayImage(final ImageView imageView, final String url) {
imageView.setTag(url);
Bitmap cacheBitmap = mCache.get(url);
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
return;
}
final ImageHolder holder = new ImageHolder();
holder.imageView = imageView;
holder.url = url;
mExecutors.submit(new Runnable() {
@Override
public void run() {
Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
mCache.put(holder.url, remoteBitmap);
holder.bitmap = remoteBitmap;
Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
message.obj = holder;
mHandler.sendMessage(message);
}
});
}
/**
* 獲取實(shí)例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
雖然說是完成了需求, 但是我們做了一個(gè)非常蠢的事情,就是去修改了ImageLoader的代碼...這明顯是違背了開閉原則的.下面介紹一下里氏替換原則和依賴倒置原則.
里氏替換原則
還是兩句話
所有引用基類的地方必須能透明地使用其子類的對象
里氏替換原則的核心原理是抽象, 抽象又依賴于繼承這個(gè)特性, 通過建立抽象, 通過抽象建立規(guī)范, 具體的實(shí)現(xiàn)在運(yùn)行時(shí)替換掉抽象, 保證系統(tǒng)的擴(kuò)展性和靈活性.
優(yōu)點(diǎn)
代碼重用, 減少創(chuàng)建類的成本, 每個(gè)子類都有父類的方法和屬性
子類與父類基本相似, 但是又有所區(qū)別
提高代碼的可擴(kuò)展性
依賴倒置原則
依賴倒置原則只帶一種特定的解耦形式, 使得高層次的模塊不依賴于低層次的模塊.
關(guān)鍵點(diǎn)
高層模塊不應(yīng)該依賴底層模塊, 兩者都應(yīng)該依賴抽象
抽象不應(yīng)該依賴細(xì)節(jié)
細(xì)節(jié)應(yīng)該依賴抽象
在Java中, 抽象就是借口或者抽象類, 細(xì)節(jié)就是實(shí)現(xiàn)類
回到ImageLoader中, 上面的圖片緩存就是直接依賴于緩存的具體實(shí)現(xiàn). 修改后我們可以依賴起父類或者接口. 但是內(nèi)存緩存和磁盤緩存的復(fù)用代碼幾乎沒有, 所以我們選擇依賴接口. 那么我們就應(yīng)該設(shè)計(jì)成下面那樣.
IImageCache
/**
* 抽象的緩存接口
*
* @author August
*
*/
public interface IImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bitmap);
}
MemoryCache
/**
* 內(nèi)存圖片緩存類
*
* @author August
*
*/
public class MemoryCache implements IImageCache {
private LruCache<String, Bitmap> mCache;
public MemoryCache() {
mCache = new LruCache<String, Bitmap>((int) (Runtime.getRuntime().maxMemory() / 4)) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
@Override
public Bitmap get(String url) {
return mCache.get(url);
}
@Override
public void put(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
DiskCache
/**
* 磁盤緩存
* @author August
*
*/
public class DiskCache implements IImageCache {
private File mDir;
public DiskCache() {
mDir = new File(Environment.getExternalStorageDirectory(), "cache");
if (!mDir.exists()) {
mDir.mkdir();
}
}
@Override
public void put(String url, Bitmap bitmap) {
OutputStream os = null;
try {
os = new FileOutputStream(new File(mDir, getDiskName(url)));
bitmap.compress(CompressFormat.JPEG, 100, os);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
bitmap.compress(CompressFormat.JPEG, 100, os);
}
@Override
public Bitmap get(String url) {
File file = new File(mDir, getDiskName(url));
Bitmap bitmap = null;
if (file.exists()) {
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
}
return bitmap;
}
private String getDiskName(String url) {
return Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
}
}
ImageLoader
public class ImageLoader {
private static final ImageLoader mInstance = new ImageLoader();
private ExecutorService mExecutors;
private IImageCache mCache = new MemoryCache();
private Downloader mDownloader = new Downloader();
private Handler mHandler = new ImageHandler();
/**
* 私有化構(gòu)造函數(shù)并初始化
*/
private ImageLoader() {
mExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/**
* 圖片顯示
*
* @param imageView
* @param url
*/
public void displayImage(final ImageView imageView, final String url) {
imageView.setTag(url);
Bitmap cacheBitmap = mCache.get(url);
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
return;
}
final ImageHolder holder = new ImageHolder();
holder.imageView = imageView;
holder.url = url;
mExecutors.submit(new Runnable() {
@Override
public void run() {
Bitmap remoteBitmap = mDownloader.downloadImage(holder.url);
mCache.put(holder.url, remoteBitmap);
holder.bitmap = remoteBitmap;
Message message = mHandler.obtainMessage(ImageHandler.SHOW_IMAGE);
message.obj = holder;
mHandler.sendMessage(message);
}
});
}
/**
* 設(shè)置緩存類型
*
* @param cache
*/
public void setImageCache(IImageCache cache) {
mCache = cache;
}
/**
* 獲取實(shí)例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
可以看到上面的ImageLoader直接依賴于Cache的抽象, 即使后面擴(kuò)展的時(shí)候需要加入其它類型的緩存, 開發(fā)者只需要關(guān)注IImageCache這個(gè)接口, 而對ImageLoader不需要有任何研究. 類似的還有Downloader的實(shí)現(xiàn), 可以修改其打開的方式.
接口隔離原則
客戶端不應(yīng)該依賴它不需要的接口
類間的依賴關(guān)系應(yīng)該建立在最小的接口上, 接口隔離原則就是將非常龐大, 臃腫的類拆分成更小的和更具體的接口.
例如上面的
OutputStream os = null;
try {
os = new FileOutputStream(new File(mDir, getDiskName(url)));
bitmap.compress(CompressFormat.JPEG, 100, os);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
其中很多代碼是沒用的, 但是異常我們必須要去捕獲, 這樣我們是不是就可以寫一個(gè)工具類去關(guān)閉文件呢? 于是有了下面的CloseUtilClose
CloseUtil
/**
* 關(guān)閉流的工具類
*
* @author August
*
*/
public class CloseUtil {
public static void close(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
OutputStream os = null;
try {
os = new FileOutputStream(new File(mDir, getDiskName(url)));
bitmap.compress(CompressFormat.JPEG, 100, os);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtil.close(os);
}
代碼立刻精簡了不少, 但是問題來了. InputStream也要有這樣的方法啊, 那么我們是不是又要重載一個(gè)方法, 參數(shù)為InputStream..
.
這時(shí)候根據(jù)接口隔離原則, 我們應(yīng)該把這種依賴關(guān)系建立在最小的接口上. 對于上面的情況, 拋出異常的是Closeable的close方法. 所以我們只要處理這種參數(shù)的就可以了, 下面的就通用了.
/**
* 關(guān)閉流的工具類
*
* @author August
*
*/
public class CloseUtil {
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
迪米特原則
- 一個(gè)類應(yīng)該對其他對象有最少的了解
什么意思?
想想, 一個(gè)類對其他對象有最少的了解, 說明了彼此間的依賴關(guān)系不是太強(qiáng), 那么對于類與類之間的耦合性就減少了. 當(dāng)一個(gè)類修改的時(shí)候, 對另一個(gè)類的影響就少了. 這就是各種設(shè)計(jì)和模式的目的.
總結(jié)
之前看到過一句話架構(gòu)是為了妥協(xié)客觀的不足, 而設(shè)計(jì)模式是為了妥協(xié)主觀上的不足. 后面文章提到的設(shè)計(jì)模式, 都是為了規(guī)范開發(fā)人員的合作. 也是圍繞著上面的六大原則進(jìn)行的.