【一】定义:
软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有的代码进行修改时,可能会将错误引入原本已经经过测试的旧代码中,破坏原有系统。因此当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。当然,在现实开发中,只通过继承的方式来升级、维护原系统只是一个理想化的愿景,因此在实际的开发过程中,修改原有代码、扩展代码往往是同时存在的。
【二】例子
经过第一轮重构之后的ImageLoader职责单一、结构清晰,但新的需求来了,加入SD卡缓存。
于是有了下面的代码:
public class DiskCache {
static String cacheDir = "sdcard/cache";
//从缓存中获取图片
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
//将图片缓存到内存中
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
因为需要将图片缓存到SD卡中,所以,ImageLoader代码有所更新,具体代码如下:
/**
*ImageLoader
**/
public class ImageLoader {
//图片缓存
ImageCache mImageCache = new ImageCache();
//SD卡缓存
DiskCache mDiskCache =new DiskCache();
boolean isUseDiskCache = false;
//线程池
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
availableProcessors());
public void displayImage(final String url, final ImageView imageView){
//判断使用哪种缓存
Bitmap bitmap=isUseDiskCache?mDiskCache.get(url):mImageCache.get(url);
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
return;
}
//没有缓存,则提交给线程池下载
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if(bitmap == null){
return ;
}
if(imageView.getTag().equals(url)){
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
public Bitmap downloadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
public void useDiskCache(boolean useDiskCache){
isUseDiskCache = useDiskCache;
}
}
代码完成,但是有一个问题,不能同时使用SD卡缓存和内存缓存,最好的方案是优先使用内存缓存,如果内存缓存没有图片再使用SD卡缓存,如果SD中野没有图片最后才从网络上获取。因此又加入了一个双缓存类DoubleCache:
public class DoubleCache{
ImageCache mMemoryCache =new ImageCache();
DiskCache mDiskCache =new DiskCache();
//先从缓存中获取图片,如果没有,再从SD卡中获取
public Bitmap get(String url) {
Bitmap bitmap=mMemoryCache(url);
if(bitmap==null){
bitmap = mDiskCache.get(url);
}
return bitmap ;
}
//将图片缓存到内存中
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url,bmp);
mDiskCache.put(url,bmp);
}
}
ImageLoader中的改动
/**
*ImageLoader
**/
public class ImageLoader {
//图片缓存
ImageCache mImageCache = new ImageCache();
//SD卡缓存
DiskCache mDiskCache =new DiskCache();
//双缓存
DoubleCache mDoubleCache =new DoubleCache();
//使用双缓存
boolean isUseDoubleCache=false;
boolean isUseDiskCache = false;
//线程池
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
availableProcessors());
public void displayImage(final String url, final ImageView imageView){
Bitmap bitmap=null;
if(isUseDoubleCache){
bitmap=mDoubleCache.get(url);
}else if(isUseDiskCache ){
bitmap=mDiskCache.get(url);
}else{
bitmap=mImageCache.get(url);
}
if (bitmap!=null){imageView.setImageBitmap(bitmap) return;}
//没有缓存,则提交给线程池下载
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if(bitmap == null){
return ;
}
if(imageView.getTag().equals(url)){
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
public Bitmap downloadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
public void useDiskCache(boolean useDiskCache){
isUseDiskCache = useDiskCache;
}
public void useDoubleCache(boolean useDoubleCache){
isUseDoubleCache = useDoubleCache;
}
}
可以看到每次新增一个缓存方法都要修改Imageloader,这样很容易引入bug,而且会使原来的代码逻辑变得越来越复杂,而且也不能自定义缓存。
软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。这就是开闭原则。
图:
代码如下:
/**
* 缓存功能
*/
public interface ImageCache {
public void put(String url, Bitmap bitmap);
public Bitmap get(String url);
}
/**
* 内存缓存
*/
public class MemeryCache implements ImageCache {
private LruCache mMemeryCache;
public MemeryCache() {
//最大内存
int maxMemery = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取1/4 的可用内存作为缓存
final int cacheSize = maxMemery / 4;
mMemeryCache = new LruCache(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
@Override
public void put(String url, Bitmap bitmap) {
mMemeryCache.put(url,bitmap);
}
@Override
public Bitmap get(String url) {
return mMemeryCache.get(url);
}
}
/**
* sd卡存储
*/
public class DiskCache implements ImageCache {
static String cacheDir = "sdcard/cache/";
@Override
public void put(String url, Bitmap bitmap) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(cacheDir + url);
bitmap.compress(Bitmap.CompressFormat.PNG,100,fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
}
/**
* 双缓存
*/
public class DoubleCache implements ImageCache {
ImageCache mMermeryCache = new MemeryCache();
ImageCache mDisCache = new DiskCache();
@Override
public void put(String url, Bitmap bitmap) {
mMermeryCache.put(url,bitmap);
mDisCache.put(url,bitmap);
}
@Override
public Bitmap get(String url) {
Bitmap bitmap = mMermeryCache.get(url);
if (bitmap == null){
bitmap = mDisCache.get(url);
}
return bitmap;
}
}
/**
* imageView 通过url展示图片
*/
public class ImageLoader {
ImageCache mImageCache = new MemeryCache();
//线程池,线程数量为CPU数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void setImgeCache(ImageCache imgeCache){
this.mImageCache = imgeCache;
}
public void displayImage(String url, ImageView imageView){
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null){
imageView.setImageBitmap(bitmap);
}
//没有缓存--下载
submitLoadRequest(url,imageView);
}
private void submitLoadRequest(String imageUrl,ImageView imageView){
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downLoadImage(imageUrl);
if (bitmap == null){
return;
}
if (imageView.getTag().equals(imageUrl)){
imageView.setImageBitmap(bitmap);
}
mImageCache.put(imageUrl,bitmap);
}
});
}
public Bitmap downLoadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
使用
ImageLoader imageLoader = new ImageLoader();
//设置缓存类型
imageLoader.setImgeCache(new DoubleCache());
//自定义缓存
imageLoader.setImgeCache(new ImageCache() {
@Override
public void put(String url, Bitmap bitmap) {
}
@Override
public Bitmap get(String url) {
return null;
}
});