在软件生命周期内,因为变化、升级、维护等原因需要对原代码进行修改时,可能会将错误引入到已经经过测试的旧代码中,从而破坏原有系统。因此当软件需要变化的时候,我们应该尽量通过扩展的方式实现变化,而不是通过修改已经存在的代码来实现。
开闭原则简单来说就是,软件中的对象(类、方法等)应该对于扩展是开放的,对于修改时关闭的。然而在显示软件开发过程中,不修改代码是不现实的,我们能做的就是尽量遵循开闭原则、此前提下我们也有必要了解什么是开闭原则,并在生产环境中灵活的趋使用它。
我们还以上一章的例子进行讲解,这里给一下上一文章的链接《面向对象的六大原则之单一职责原则》上文将代码重构之后结构较为清晰了,然而我们知道Android的应用内存有限,内存缓存的图片具有易失性,应用重新启动需要重新下载,会消耗用户比较多的流量,这个时候应该怎么办呢?这个时候我们可以加入SD卡缓存。我们先来看一下一般性的代码实现方式:
先增加一个实现SD卡缓存的类
public class DiskCache {
//声明存储路径
static String cacheDir="sdcard/cache/";
/**
* 取缓存图片
* @param url
* @return
*/
public Bitmap get(String url){
return BitmapFactory.decodeFile(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();
}
}
}
}
}
上一篇文章的实现内存缓存的ImageCache类
public class ImageCache {
LruCache mImageCache;
public ImageCache(){
initImageCache();
}
private void initImageCache()
{
//计算可以使用的最大内存
final int maxMemory= (int) (Runtime.getRuntime().maxMemory()/1024);
//取可用内存的1/4
final int cachesize=maxMemory/4;
mImageCache=new LruCache(cachesize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
}
/**
* 将图片放入缓存
*/
public void put(String url,Bitmap bitmap)
{
mImageCache.put(url,bitmap);
}
/**
* 将图片从缓存中取出
*/
public Bitmap get(String url)
{
return mImageCache.get(url);
}
}
最后是一个负责加载图片的ImageLoader类:
public class ImageLoader {
DiskCache diskCache = new DiskCache();
ImageCache imageCache = new ImageCache();
boolean isUseDiskCache = false;
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = isUseDiskCache ? diskCache.get(url) : imageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}else{
// 没有图片则从网络上下载图片,并放入缓存
// downBitmap(url);
}
}
public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
/**
* 通过URL下载图片,并返回Bitmap
*
* @param imageUrl
* @return
*/
public Bitmap downBitmap(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
最后我们怎么使用这个类呢?请看以下代码:
ImageLoader imageloader=new ImageLoader();
//使用SD卡缓存
imageloader.setUseDisCache(true);
//使用内存进行缓存
imageloader.setUseDisCache(false);
以上代码确实解决了内存缓存图片易丢失的问题,然而功能实现的是比较僵硬的,用户使用内存缓存,就不能用SD卡缓存。这对使用这一功能的程序员来说是比较难以接受的。我们熟知的正确的缓存功能是应该优先使用内存缓存,如果内存缓存中没有再使用SD卡缓存,如果SD卡中也没有图片最后再从网络中获取图片,并放入缓存中。我们通过代码看看怎么来实现:
/**
*首先要对SD卡缓存和内存缓存进行整合,实现一个双缓存,使其先从内存获取图片,
再从SD卡中获取图片。我们来写一下整合后的代码:
*/
public class DoubleCache{
ImageCache mMemoryCache=new ImageCache();
DiskCache mDiskCache=new DiskCache();
public Bitmap get(String url)
{
//从缓存中获取图片
Bitmap bitmap=mMemoryCache.get(url);
if(bitmap==null){
bitmap=mDiskCache.get(url)
}
}
public void put(String url,Bitmap bmp){
mMemoryCache.put(bmp);
mDiskCache.put(bmp)
}
}
/**
*以下再次修改一下ImageLoader
*/
public class ImageLoader {
DiskCache diskCache = new DiskCache();//SD卡缓存
ImageCache imageCache = new ImageCache();//内存缓存
DoubleCache doubleCache=new DoubleCache();//新增双缓存功能类
boolean isDoubleCache=false;//是否使用双缓存代码
boolean isUseDiskCache = false;是否使用SD卡缓存
ExecutorService mExecutorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = null;
if(isDoubleCache){
bitmap=doubleCache.get(url);
}else if(isUseDiskCache)
{
bitmap=diskCache.get(url);
}else{
bitmap=imageCache.get(url);
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
public void setDoubleCache(boolean useDoubleCache) {
isDoubleCache = useDoubleCache;
}
以上代码在内存缓存、SD缓存的基础上再次添加了双缓存,用户可以根据自己的需求选择使用SD卡缓存,内存缓存或者双缓存。这样的代码虽说有功能,但是看着实在太丑陋了,项目中的需求各种各样,假如十个人都需要使用与其他人不同的缓存策略,岂不是要在代码中添加十几个if...else..语句吗?不断地修改代码,难保不会出现BUG。这是非常不符合开闭原则的。我们对这个功能依照开闭原则来做一下整理:
/**
*首先声明一个接口,提取各种缓存的公共方法
*/
public interface ImageCache{
//获取Bitmap
public Bitmap get(String url);
//将图片放入缓存中
public void put(String url,Bitmap bmp);
}
/**
*接着实现内存缓存、SD卡缓存、双缓存等其他自定义的缓存策略全部通过ImageCache接口来实现。代码如下
*/
public class MemoryCache implenments ImageCache{
private LruCache mMemoryCache;
public MemoryCache(){
}
@Override
public Bitmap get(String url){
return mMemoryCache.get(url);
}
@Override
public void put(String url,Bitmap bmp){
mMemoryCache.put(url,bmp);
}
}
public class DiskCache implenments ImageCache{
//声明存储路径
static String cacheDir="sdcard/cache/";
/**
* 取缓存图片
* @param url
* @return
*/
@Override
public Bitmap get(String url){
return BitmapFactory.decodeFile(url);
}
@Override
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();
}
}
}
}
}
public class DoubleCache implenments ImageCache{
ImageCache mMemoryCache=new MemoryCache();
ImageCache mDiskCache=new DiskCache();
public Bitmap get(String url)
{
//从缓存中获取图片
Bitmap bitmap=mMemoryCache.get(url);
if(bitmap==null){
bitmap=mDiskCache.get(url)
}
}
public void put(String url,Bitmap bmp){
mMemoryCache.put(bmp);
mDiskCache.put(bmp)
}
}
我们再修改一下ImageLoader,在这个类中我们就可以使用ImageCache来代替实现各个缓存策略。代码如下:
public class ImageLoader {
//内存缓存为其默认缓存
ImageCache imageCache = new MemoryCache();
ExecutorService mExecutorService =Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//通过这个方法制定缓存策略
public void setImageCache(ImageCache cache){
imageCache=cache;
}
public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = imageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}else{
// 没有图片则从网络上下载图片,并放入缓存。此处代码自行实现
// downBitmap(url);
}
}
在上述代码中,通过setImageCache()方法指定不同的缓存策略即可,这样不仅能够使Imageloader更简单、健壮,也使得ImageLoader的可扩展性灵活性更高以上三种缓存实现完全不一样,共同点是都实现了ImageCache接口。用户需要自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,通过setImageCache方法注入ImageLoader中即可,这样以来ImageLoader就实现了千变万化的缓存策略,并且对于扩展这些缓存策略并不会导致ImageLoader类的修改。这就是我们说的对扩展开放,对修改封闭的软件开闭原则。遵循开闭原则的重要手段就是通过抽象。