定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
也就是说,如果修改或者添加一个功能,应该是通过扩展原来的代码,而不是通过修改原来的代码。
比如,在图片加载类中,有内存缓存,磁盘缓存,还有双缓存:
//内存缓存
public class MemoryCache {
LruCache<String, Bitmap> mLruCache;
public MemoryCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 4;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
public Bitmap get(String url) {
return return mLruCache.get(url);
}
public void put(String url, Bitmap bmp) {
// do something
}
}
//磁盘缓存
public class DiskCache {
public Bitmap get(String url) {
return BitmapFactory.decodeFile(url);
}
public void put(String url, Bitmap bmp) {
//do something
}
}
//双缓存
public class DoubleCache {
MemoryCache mMemoryCache = new MemoryCache();
DiskCache mDiskCache = new DiskCache();
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap==null){
bitmap = mDiskCache.get(url);
}
return bitmap;
}
public void put(String url, Bitmap bmp) {
//do something
}
}
如果不用开闭原则,那么在图片加载类中,可能会这么写:
public class ImageLoader {
MemoryCache mMemoryCache = new MemoryCache();
DiskCache mDiskCache = new DiskCache();
DoubleCache mDoubleCache = new DoubleCache();
boolean isDiskCache = false; //使用磁盘缓存
boolean isDoubleCache = false; //使用双缓存
public ImageLoader() {
}
public void displayImage(String url, ImageView mImageView) {
Bitmap bitmap = null;
if (isDoubleCache) {
bitmap = mDoubleCache.get(url);
} else if (isDiskCache) {
bitmap = mDiskCache.get(url);
} else {
bitmap = mMemoryCache.get(url);
}
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
} else {
//没有缓存,下载图片
}
}
public void setDiskCache(boolean diskCache) {
isDiskCache = diskCache;
}
public void setDoubleCache(boolean doubleCache) {
isDoubleCache = doubleCache;
}
}
这样写,可能会出现这样的问题:
if-else 的判断条件太多,如果写错了其中一个,就会化很多时间去查找和解决,ImageLoader 类也会变得臃肿,而且用户不能自己实现缓存注入到 ImageLoader 中,可扩展性差。
那么,通过分析可以知道,每个缓存都用 set 和 put 方法,那么可以把它抽象出来。
UML图:
UML
这样通过接口就可以实现不同的缓存,而且不需要改变 ImageLoader 的代码。
首先创建接口 ImageCache:
public interface ImageCache {
void put(String url, Bitmap bmp);
Bitmap get(String url);
}
然后将原来的三种缓存都继承它:
//内存缓存
public class MemoryCache implements ImageCache {
LruCache<String, Bitmap> mLruCache;
public MemoryCache() {
//初始化LruCache
}
@Override
public void put(String url, Bitmap bmp) {
mLruCache.put(url, bmp);
}
@Override
public Bitmap get(String url) {
return mLruCache.get(url);
}
}
//磁盘缓存
public class DiskCache implements ImageCache{
@Override
public void put(String url, Bitmap bmp) {
//将Bitmap写入文件
}
@Override
public Bitmap get(String url) {
return BitmapFactory.decodeFile(url); //从文件中获取Bitmap
}
}
//双缓存
public class DoubleCache implements ImageCache {
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();
@Override
public void put(String url, Bitmap bmp) {
mMemoryCache.put(url, bmp);
mDiskCache.put(url, bmp);
}
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
}
那么,ImageLoader 类就可以改写成:
public class ImageLoader {
ImageCache mImageCache = new MemoryCache();
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
public ImageLoader() {
}
public void displayImage(String url, ImageView mImageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
}
//图片没缓存,下载
// do something
}
}
比原来的简洁很多。
那么在使用的时候这样:
ImageLoader imageLoader = new ImageLoader();
//选择使用内存缓存
imageLoader.setImageCache(new MemoryCache());
//选择使用磁盘缓存
imageLoader.setImageCache(new DiskCache());
//选择使用双缓存
imageLoader.setImageCache(new DoubleCache());
//不选择封装好的缓存,自己实现缓存
imageLoader.setImageCache(new ImageCache() {
@Override
public void put(String url, Bitmap bmp) {
}
@Override
public Bitmap get(String url) {
return null;
}
});
可以看到,用户通过 setImageCache 方法可以自由设置缓存的实现方式,而不用通过修改 ImageLoader 来实现。setImageCache 就是通常说的依赖注入。使得 ImageLoader 类更加健壮,这就是开闭原则。
这里,应该明白开闭原则是怎么一回事了。
参考:《Android源码设计模式解析与实践》