一、定义
开闭原则(OCP:Open Closed Principle):软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。
二、场景运用
回顾下 Android设计模式之单一职责原则 里图片加载器的代码,通过内存缓存确实解决了每次从网络加载图片的问题,但是当应用重启后,原来加载过的图片就会丢失,又要重新下载,导致加载缓慢,而且还耗费流量。
此时小明考虑引入 SDCard 缓存,这样下载过的图片就会缓存到本地,即使重启应用也不需要重新下载了。
于是乎,小明开始了 coding。
/**
* 图片加载器
*/
public class ImageLoader {
//图片缓存
private final ImageLruCache imageLruCache = new ImageLruCache();
//SDCard缓存
private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
//线程池,线程数量为Cpu数量
private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//Ui Handler
private final Handler uiHandler = new Handler(Looper.getMainLooper());
//是否使用SDCard缓存
private boolean useSDCardCache = false;
/**
* 设置是否使用SDCard缓存
*
* @param useSDCardCache
*/
public void setUseSDCardCache(boolean useSDCardCache) {
this.useSDCardCache = useSDCardCache;
}
/**
* 展示图片
*
* @param imageUrl
* @param imageView
*/
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = useSDCardCache ? imageSDCardCache.get(imageView.getContext(), imageUrl) : imageLruCache.get(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(imageUrl);
executorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap != null) {
if (imageView.getTag().equals(imageUrl)) {
postToImageView(imageView, bitmap);
}
if (useSDCardCache) {
imageSDCardCache.put(imageView.getContext(), imageUrl, bitmap);
} else {
imageLruCache.put(imageUrl, bitmap);
}
}
}
});
}
/**
* 更新到ImageView上
*
* @param imageView
* @param bitmap
*/
private void postToImageView(ImageView imageView, Bitmap bitmap) {
uiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
/**
* 下载图片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
HttpURLConnection connection = null;
try {
URL url = new URL(imageUrl);
connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() == 200) {
//忽略图片压缩过程...
return BitmapFactory.decodeStream(connection.getInputStream());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
}
/**
* 图片缓存
*/
public class ImageLruCache {
//图片缓存
private final LruCache<String, Bitmap> imageLruCache;
public ImageLruCache() {
//可使用最大内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取1/4可用内存作为图片缓存
final int imageLruCacheSize = maxMemory / 4;
imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 存入图片
*
* @param imageUrl
* @param bitmap
*/
public void put(String imageUrl, Bitmap bitmap) {
imageLruCache.put(imageUrl, bitmap);
}
/**
* 取出图片
*
* @param imageUrl
* @return
*/
public Bitmap get(String imageUrl) {
return imageLruCache.get(imageUrl);
}
}
/**
* SDCard缓存
*/
public class ImageSDCardCache {
public Bitmap get(Context context, String imageUrl) {
return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
}
public void put(Context context, String imageUrl, Bitmap bitmap) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
通过 setUseSDCardCache 方法就可以让用户设置不同的缓存,小明很满意,于是提交给主管做代码审查。
主管:思路是对的,但还是有问题,就是使用内存缓存时用户就不能使用 SDCard 缓存。用户需要的是两种策略的综合,首先优先使用内存缓存,如果内存缓存没有图片再使用 SDCard 缓存,如果 SDCard 缓存也没有图片,最后才从网络上获取。
小明(脑子仿佛如梦初醒):我马上优化下。
于是,小明开始了 coding。
/**
* 图片加载器
*/
public class ImageLoader {
//图片缓存
private final ImageLruCache imageLruCache = new ImageLruCache();
//SDCard缓存
private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
//双缓存
private final DoubleCache doubleCache = new DoubleCache();
//线程池,线程数量为Cpu数量
private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//Ui Handler
private final Handler uiHandler = new Handler(Looper.getMainLooper());
//是否使用SDCard缓存
private boolean useSDCardCache = false;
//是否使用双缓存
private boolean useDoubleCache = false;
/**
* 设置是否使用SDCard缓存
*
* @param useSDCardCache
*/
public void setUseSDCardCache(boolean useSDCardCache) {
this.useSDCardCache = useSDCardCache;
}
/**
* 是否使用双缓存
*
* @param useDoubleCache
*/
public void setUseDoubleCache(boolean useDoubleCache) {
this.useDoubleCache = useDoubleCache;
}
/**
* 展示图片
*
* @param imageUrl
* @param imageView
*/
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = null;
if (useDoubleCache) {
bitmap = doubleCache.get(imageView.getContext(), imageUrl);
} else if (useSDCardCache) {
bitmap = imageSDCardCache.get(imageView.getContext(), imageUrl);
} else {
bitmap = imageLruCache.get(imageUrl);
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(imageUrl);
executorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap != null) {
if (imageView.getTag().equals(imageUrl)) {
postToImageView(imageView, bitmap);
}
if (useDoubleCache) {
doubleCache.put(imageView.getContext(), imageUrl, bitmap);
} else if (useSDCardCache) {
imageSDCardCache.put(imageView.getContext(), imageUrl, bitmap);
} else {
imageLruCache.put(imageUrl, bitmap);
}
}
}
});
}
/**
* 更新到ImageView上
*
* @param imageView
* @param bitmap
*/
private void postToImageView(ImageView imageView, Bitmap bitmap) {
uiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
/**
* 下载图片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
HttpURLConnection connection = null;
try {
URL url = new URL(imageUrl);
connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() == 200) {
//忽略图片压缩过程...
return BitmapFactory.decodeStream(connection.getInputStream());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
}
/**
* 图片缓存
*/
public class ImageLruCache {
//图片缓存
private final LruCache<String, Bitmap> imageLruCache;
public ImageLruCache() {
//可使用最大内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取1/4可用内存作为图片缓存
final int imageLruCacheSize = maxMemory / 4;
imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 存入图片
*
* @param imageUrl
* @param bitmap
*/
public void put(String imageUrl, Bitmap bitmap) {
imageLruCache.put(imageUrl, bitmap);
}
/**
* 取出图片
*
* @param imageUrl
* @return
*/
public Bitmap get(String imageUrl) {
return imageLruCache.get(imageUrl);
}
}
/**
* SDCard缓存
*/
public class ImageSDCardCache {
public Bitmap get(Context context, String imageUrl) {
return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
}
public void put(Context context, String imageUrl, Bitmap bitmap) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 双缓存
*/
public class DoubleCache {
//图片缓存
private final ImageLruCache imageLruCache = new ImageLruCache();
//SDCard缓存
private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
public Bitmap get(Context context, String imageUrl) {
Bitmap bitmap = imageLruCache.get(imageUrl);
if (bitmap == null) {
bitmap = imageSDCardCache.get(context, imageUrl);
}
return bitmap;
}
public void put(Context context, String imageUrl, Bitmap bitmap) {
imageLruCache.put(imageUrl, bitmap);
imageSDCardCache.put(context, imageUrl, bitmap);
}
}
复制代码
小明通过几处修改就完成了这功能,顿时感到自己 Android 开发得心应手了。
主管:你每次加入新的缓存都要修改原来的代码,这样很容易引入 Bug,而且代码逻辑也变复杂了。按照这种设计,用户也不能完成自定义缓存。
小明听得云里雾里的,主管只能亲自操刀了。
/**
* 图片加载器
*/
public class ImageLoader {
//图片缓存
private IImageCache imageCache = new ImageLruCache();
//线程池,线程数量为Cpu数量
private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//Ui Handler
private final Handler uiHandler = new Handler(Looper.getMainLooper());
/**
* 设置图片缓存
*
* @param imageCache
*/
public void setImageCache(IImageCache imageCache) {
this.imageCache = imageCache;
}
/**
* 展示图片
*
* @param imageUrl
* @param imageView
*/
public void displayImage(String imageUrl, ImageView imageView) {
Bitmap bitmap = imageCache.get(imageView.getContext(), imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(imageUrl);
executorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap != null) {
if (imageView.getTag().equals(imageUrl)) {
postToImageView(imageView, bitmap);
}
imageCache.put(imageView.getContext(), imageUrl, bitmap);
}
}
});
}
/**
* 更新到ImageView上
*
* @param imageView
* @param bitmap
*/
private void postToImageView(ImageView imageView, Bitmap bitmap) {
uiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
/**
* 下载图片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
HttpURLConnection connection = null;
try {
URL url = new URL(imageUrl);
connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() == 200) {
//忽略图片压缩过程...
return BitmapFactory.decodeStream(connection.getInputStream());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
}
/**
* 抽象缓存接口
*/
public interface IImageCache {
/**
* 存入图片
*
* @param context
* @param imageUrl
* @return
*/
Bitmap get(Context context, String imageUrl);
/**
* 取出图片
*
* @param context
* @param imageUrl
* @param bitmap
*/
void put(Context context, String imageUrl, Bitmap bitmap);
}
/**
* 图片缓存
*/
public class ImageLruCache implements IImageCache {
//图片缓存
private final LruCache<String, Bitmap> imageLruCache;
public ImageLruCache() {
//可使用最大内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取1/4可用内存作为图片缓存
final int imageLruCacheSize = maxMemory / 4;
imageLruCache = new LruCache<String, Bitmap>(imageLruCacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
@Override
public Bitmap get(Context context, String imageUrl) {
return imageLruCache.get(imageUrl);
}
@Override
public void put(Context context, String imageUrl, Bitmap bitmap) {
imageLruCache.put(imageUrl, bitmap);
}
}
/**
* SDCard缓存
*/
public class ImageSDCardCache implements IImageCache{
@Override
public Bitmap get(Context context, String imageUrl) {
return BitmapFactory.decodeFile(context.getCacheDir().getAbsolutePath() + imageUrl);
}
@Override
public void put(Context context, String imageUrl, Bitmap bitmap) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(context.getCacheDir().getAbsolutePath() + imageUrl);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 双缓存
*/
public class DoubleCache implements IImageCache {
//图片缓存
private final ImageLruCache imageLruCache = new ImageLruCache();
//SDCard缓存
private final ImageSDCardCache imageSDCardCache = new ImageSDCardCache();
@Override
public Bitmap get(Context context, String imageUrl) {
Bitmap bitmap = imageLruCache.get(context, imageUrl);
if (bitmap == null) {
bitmap = imageSDCardCache.get(context, imageUrl);
}
return bitmap;
}
@Override
public void put(Context context, String imageUrl, Bitmap bitmap) {
imageLruCache.put(context, imageUrl, bitmap);
imageSDCardCache.put(context, imageUrl, bitmap);
}
}
复制代码
上述代码中,用户通过 setImageCache 方法就可以注入不同的缓存实现,因为所有的缓存都有一个特点,那就是实现了 IImageCache 接口,这样就使得 ImageLoader 的扩展性更强了。
小明此时茅塞顿开。
三、总结
当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有代码来实现。“应该尽量”这四字说明并不是不能修改原始类,当嗅到原来代码“腐化气味”时,应该尽早重构。