最近在学习设计模式, 搜索大量的资料发现很多资料都是只是说明这些设计模式是怎样的, 而没有说明实际用途, 大量的资料都是重叠重复的. 虽说入门, 但是给出例子之后就没有再深入下去了. 学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();
}
}
}
}
迪米特原则
- 一个类应该对其他对象有最少的了解
什么意思?
想想, 一个类对其他对象有最少的了解, 说明了彼此间的依赖关系不是太强, 那么对于类与类之间的耦合性就减少了. 当一个类修改的时候, 对另一个类的影响就少了. 这就是各种设计和模式的目的.
总结
之前看到过一句话架构是为了妥协客观的不足, 而设计模式是为了妥协主观上的不足. 后面文章提到的设计模式, 都是为了规范开发人员的合作. 也是围绕着上面的六大原则进行的.
网友评论