最近在學習設計模式, 搜索大量的資料發(fā)現(xiàn)很多資料都是只是說明這些設計模式是怎樣的, 而沒有說明實際用途, 大量的資料都是重疊重復的. 雖說入門, 但是給出例子之后就沒有再深入下去了. 學Android開發(fā)的, 很多時候看完用Java寫的設計模式代碼, 但是卻不知道怎么應用到實際的項目開發(fā)中去. 所以打算根據(jù)自己的?一些拙見, 能把經(jīng)常使用的設計方法寫成文章互相交流.
從ImageLoader說起
如果需要寫一個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);
}
}
};
};
/**
* 私有化構造函數(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;
}
/**
* 獲取實例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
/**
* 顯示圖片消息數(shù)據(jù)傳輸對象
*
* @author August
*
*/
static final class ImageHolder {
ImageView imageView;
Bitmap bitmap;
String url;
}
}
好, 至此一個ImageLoader就已經(jīng)寫完了.我們下面根據(jù)面向對象的六大原則對它進行改造
單一職責原則
書面語就兩句重要的話:
就一個類而言,應該僅有一個引起它變化的原因
一個類中應該是一組相關性很高的函數(shù)和數(shù)據(jù)的封裝
上面我們把ImageLoader的各部分都卸載一個類中, 所以已經(jīng)違背了該原則, 我們應該盡量地去簡化每個類的工作量. 把相關度不高的部分分離出去. 于是我們就有了下面的改造.
Downloader
/**
* 下載網(wǎng)絡圖片
*
* @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();
/**
* 私有化構造函數(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);
}
});
}
/**
* 獲取實例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
可以看到現(xiàn)在我們的ImageLoader已經(jīng)是有模有樣地分開了幾個類.
開閉原則
也是兩句話作總結
軟件中的對象(類 模塊 函數(shù)等)應該對于擴展是開放的, 但是對于修改是封閉的.
程序一旦開發(fā)完成, 程序中的一個類的實現(xiàn)只應該因錯誤而被修改, 新的或者改變的特性應該通過新建不同的類實現(xiàn), 新建的類可以通過集成的方式來重用原來的代碼.
上面的ImageLoader只在內存中緩存, 后來發(fā)現(xiàn)一級的緩存是行不通的. 因為Bitmap占用的內存太, 很容易被回收. 所以我們需要使用磁盤緩存. 然后我們增加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();
/**
* 私有化構造函數(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);
}
});
}
/**
* 獲取實例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
什么??? 只有磁盤緩存又不夠快??? 要做二級緩存???? 代碼又要改...也不怎么難, 我們添加一個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();
/**
* 私有化構造函數(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);
}
});
}
/**
* 獲取實例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
雖然說是完成了需求, 但是我們做了一個非常蠢的事情,就是去修改了ImageLoader的代碼...這明顯是違背了開閉原則的.下面介紹一下里氏替換原則和依賴倒置原則.
里氏替換原則
還是兩句話
所有引用基類的地方必須能透明地使用其子類的對象
里氏替換原則的核心原理是抽象, 抽象又依賴于繼承這個特性, 通過建立抽象, 通過抽象建立規(guī)范, 具體的實現(xiàn)在運行時替換掉抽象, 保證系統(tǒng)的擴展性和靈活性.
優(yōu)點
代碼重用, 減少創(chuàng)建類的成本, 每個子類都有父類的方法和屬性
子類與父類基本相似, 但是又有所區(qū)別
提高代碼的可擴展性
依賴倒置原則
依賴倒置原則只帶一種特定的解耦形式, 使得高層次的模塊不依賴于低層次的模塊.
關鍵點
高層模塊不應該依賴底層模塊, 兩者都應該依賴抽象
抽象不應該依賴細節(jié)
細節(jié)應該依賴抽象
在Java中, 抽象就是借口或者抽象類, 細節(jié)就是實現(xiàn)類
回到ImageLoader中, 上面的圖片緩存就是直接依賴于緩存的具體實現(xiàn). 修改后我們可以依賴起父類或者接口. 但是內存緩存和磁盤緩存的復用代碼幾乎沒有, 所以我們選擇依賴接口. 那么我們就應該設計成下面那樣.
IImageCache
/**
* 抽象的緩存接口
*
* @author August
*
*/
public interface IImageCache {
public Bitmap get(String url);
public void put(String url, Bitmap bitmap);
}
MemoryCache
/**
* 內存圖片緩存類
*
* @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();
/**
* 私有化構造函數(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);
}
});
}
/**
* 設置緩存類型
*
* @param cache
*/
public void setImageCache(IImageCache cache) {
mCache = cache;
}
/**
* 獲取實例
*
* @return
*/
public static ImageLoader getInstance() {
return mInstance;
}
}
可以看到上面的ImageLoader直接依賴于Cache的抽象, 即使后面擴展的時候需要加入其它類型的緩存, 開發(fā)者只需要關注IImageCache這個接口, 而對ImageLoader不需要有任何研究. 類似的還有Downloader的實現(xiàn), 可以修改其打開的方式.
接口隔離原則
客戶端不應該依賴它不需要的接口
類間的依賴關系應該建立在最小的接口上, 接口隔離原則就是將非常龐大, 臃腫的類拆分成更小的和更具體的接口.
例如上面的
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();
}
}
}
其中很多代碼是沒用的, 但是異常我們必須要去捕獲, 這樣我們是不是就可以寫一個工具類去關閉文件呢? 于是有了下面的CloseUtilClose
CloseUtil
/**
* 關閉流的工具類
*
* @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也要有這樣的方法啊, 那么我們是不是又要重載一個方法, 參數(shù)為InputStream..
.
這時候根據(jù)接口隔離原則, 我們應該把這種依賴關系建立在最小的接口上. 對于上面的情況, 拋出異常的是Closeable的close方法. 所以我們只要處理這種參數(shù)的就可以了, 下面的就通用了.
/**
* 關閉流的工具類
*
* @author August
*
*/
public class CloseUtil {
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
迪米特原則
- 一個類應該對其他對象有最少的了解
什么意思?
想想, 一個類對其他對象有最少的了解, 說明了彼此間的依賴關系不是太強, 那么對于類與類之間的耦合性就減少了. 當一個類修改的時候, 對另一個類的影響就少了. 這就是各種設計和模式的目的.
總結
之前看到過一句話架構是為了妥協(xié)客觀的不足, 而設計模式是為了妥協(xié)主觀上的不足. 后面文章提到的設計模式, 都是為了規(guī)范開發(fā)人員的合作. 也是圍繞著上面的六大原則進行的.