最近在学习设计模式, 搜索大量的资料发现很多资料都是只是说明这些设计模式是怎样的, 而没有说明实际用途, 大量的资料都是重叠重复的. 虽说入门, 但是给出例子之后就没有再深入下去了. 学Android开发的, 很多时候看完用Java写的设计模式代码, 但是却不知道怎么应用到实际的项目开发中去. 所以打算根据自己的�一些拙见, 能把经常使用的设计方法写成文章互相交流.
从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);
}
}
};
};
/**
* 私有化构造函数并初始化
*/
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;
}
/**
* 显示图片消息数据传输对象
*
* @author August
*
*/
static final class ImageHolder {
ImageView imageView;
Bitmap bitmap;
String url;
}
}
好, 至此一个ImageLoader就已经写完了.我们下面根据面向对象的六大原则对它进行改造
单一职责原则
书面语就两句重要的话:
就一个类而言,应该仅有一个引起它变化的原因
一个类中应该是一组相关性很高的函数和数据的封装
上面我们把ImageLoader的各部分都卸载一个类中, 所以已经违背了该原则, 我们应该尽量地去简化每个类的工作量. 把相关度不高的部分分离出去. 于是我们就有了下面的改造.
Downloader
/**
* 下载网络图片
*
* @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
/**
* 显示图片消息的数据传输对象
*
* @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();
/**
* 私有化构造函数并初始化
*/
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已经是有模有样地分开了几个类.
开闭原则
也是两句话作总结
软件中的对象(类 模块 函数等)应该对于扩展是开放的, 但是对于修改是封闭的.
程序一旦开发完成, 程序中的一个类的实现只应该因错误而被修改, 新的或者改变的特性应该通过新建不同的类实现, 新建的类可以通过集成的方式来重用原来的代码.
上面的ImageLoader只在内存中缓存, 后来发现一级的缓存是行不通的. 因为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();
/**
* 私有化构造函数并初始化
*/
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();
/**
* 私有化构造函数并初始化
*/
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的代码...这明显是违背了开闭原则的.下面介绍一下里氏替换原则和依赖倒置原则.
里氏替换原则
还是两句话
所有引用基类的地方必须能透明地使用其子类的对象
里氏替换原则的核心原理是抽象, 抽象又依赖于继承这个特性, 通过建立抽象, 通过抽象建立规范, 具体的实现在运行时替换掉抽象, 保证系统的扩展性和灵活性.
优点
代码重用, 减少创建类的成本, 每个子类都有父类的方法和属性
子类与父类基本相似, 但是又有所区别
提高代码的可扩展性
依赖倒置原则
依赖倒置原则只带一种特定的解耦形式, 使得高层次的模块不依赖于低层次的模块.
关键点
高层模块不应该依赖底层模块, 两者都应该依赖抽象
抽象不应该依赖细节
细节应该依赖抽象
在Java中, 抽象就是借口或者抽象类, 细节就是实现类
回到ImageLoader中, 上面的图片缓存就是直接依赖于缓存的具体实现. 修改后我们可以依赖起父类或者接口. 但是内存缓存和磁盘缓存的复用代码几乎没有, 所以我们选择依赖接口. 那么我们就应该设计成下面那样.
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();
/**
* 私有化构造函数并初始化
*/
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的抽象, 即使后面扩展的时候需要加入其它类型的缓存, 开发者只需要关注IImageCache这个接口, 而对ImageLoader不需要有任何研究. 类似的还有Downloader的实现, 可以修改其打开的方式.
接口隔离原则
客户端不应该依赖它不需要的接口
类间的依赖关系应该建立在最小的接口上, 接口隔离原则就是将非常庞大, 臃肿的类拆分成更小的和更具体的接口.
例如上面的
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也要有这样的方法啊, 那么我们是不是又要重载一个方法, 参数为InputStream..
.
这时候根据接口隔离原则, 我们应该把这种依赖关系建立在最小的接口上. 对于上面的情况, 抛出异常的是Closeable的close方法. 所以我们只要处理这种参数的就可以了, 下面的就通用了.
/**
* 关闭流的工具类
*
* @author August
*
*/
public class CloseUtil {
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
迪米特原则
- 一个类应该对其他对象有最少的了解
什么意思?
想想, 一个类对其他对象有最少的了解, 说明了彼此间的依赖关系不是太强, 那么对于类与类之间的耦合性就减少了. 当一个类修改的时候, 对另一个类的影响就少了. 这就是各种设计和模式的目的.
总结
之前看到过一句话架构是为了妥协客观的不足, 而设计模式是为了妥协主观上的不足. 后面文章提到的设计模式, 都是为了规范开发人员的合作. 也是围绕着上面的六大原则进行的.