面向对象的六大原则(二)-- 开闭原则(切合Android,ImageLoader)

二:开闭原则(Open Close Principle)

定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改时封闭的。(虽然在实战中修改原有代码,扩展代码往往是难以避免的)
遵循开闭原则的重要手段应该是通过抽象!!!

承接与上一篇文章,单一职责原则:点击打开链接

问题代码:
ImageLoader.java
/**
 * 图片加载类
 */
public  class ImageLoader {
    // 图片缓存
    ImageCache mImageCache = new ImageCache() ;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());

    // 加载图片
    public  void displayImage(final String url, final ImageView imageView) {
        Bitmap bitmap = mImageCache.get(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        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;
    }
}   

ImageCache.java
public class ImageCache {
    // 图片LRU缓存
    LruCache<String, Bitmap> mImageCache;

    public ImageCache() {
        initImageCache();
    }

    private void initImageCache() {
         // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 取四分之一的可用内存作为缓存
        final int cacheSize = maxMemory / 4;
        mImageCache = new LruCache<String, Bitmap>(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) ;
    }
}

以上代码虽然功能分离了,但是每次打开应用都要重新下载图片,非常耗费流量,而且下载图片也是一个耗时操作。这简直不能忍。。。

于是加入DiskCache.java
public class DiskCache {
    // 为了简单起见临时写个路径,在开发中请避免这种写法 !
    static String cacheDir = "sdcard/cache/";
     // 从缓存中获取图片
    public Bitmap get(String url) {
        return BitmapFactory.decodeFile(cacheDir + url);
    }
 
    // 将图片缓存到内存中
    public  void  put(String url, Bitmap bmp) {
       FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bmp.compress(CompressFormat.PNG, 100, fileOutputStream);
      } catch (FileNotFoundException e) {
            e.printStackTrace();
      } final ly {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
              } catch (IOException e) {
                    e.printStackTrace();
             }
          }
      }
    }
}
因为需要将图片缓存到SD卡中,所以,ImageLoader代码有所更新,具体代码如下:
public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // SD卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 是否使用SD卡缓存
    boolean isUseDiskCache = false;
    // 线程池,线程数量为CPU的数量
    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;
       }
        // 没有缓存,则提交给线程池进行下载
    }
 
    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache ;
    }
}
 

新增了一个DiskCache类和往ImageLoader类中加入了少量代码就添加了SD卡缓存的功能,用户可以通过useDiskCache方法来对使用哪种缓存进行设置,例如:
ImageLoader imageLoader = new ImageLoader() ;
 // 使用SD卡缓存
imageLoader.useDiskCache(true);
// 使用内存缓存
imageLoader.useDiskCache(false);
 

但是!!!有些明显的问题,就是使用内存缓存时用户就不能使用SD卡缓存,类似的,使用SD卡缓存时用户就不能使用内存缓存。用户需要这两种策略的综合,首先缓存优先使用内存缓存,如果内存缓存没有图片再使用SD卡缓存,如果SD卡中也没有图片最后才从网络上获取,这才是最好的缓存策略。
/**
 * 双缓存。获取图片时先从内存缓存中获取,如果内存中没有缓存该图片,再从SD卡中获取。
 *  缓存图片也是在内存和SD卡中都缓存一份
 */
public class DoubleCache {
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();
 
    // 先从内存缓存中获取图片,如果没有,再从SD卡中获取
    public   Bitmap get(String url) {
       Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
       }
        return  bitmap;
    }
 
    // 将图片缓存到内存和SD卡中
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
   }
}

再看看最新的ImageLoader类吧,代码更新也不多:
public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // SD卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 双缓存
    DoubleCache mDoubleCache = new DoubleCache() ;
    // 使用SD卡缓存
    boolean isUseDiskCache = false;
    // 使用双缓存
    boolean isUseDoubleCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
 
 
    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bmp = null;
         if (isUseDoubleCache) {
            bmp = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bmp = mDiskCache.get(url);
        } else {
            bmp = mImageCache.get(url);
        }
 
         if ( bmp != null ) {
            imageView.setImageBitmap(bmp);
        }
        // 没有缓存,则提交给线程池进行异步下载图片
    }
 
    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache ;
    }
 
    public void useDoubleCache(boolean useDoubleCache) {
        isUseDoubleCache = useDoubleCache ;
    }
}

这样做固然有些得意,问题依旧有:每次在程序中加入新的缓存实现时都需要修改ImageLoader类,然后通过一个布尔变量来让用户使用哪种缓存,因此,就使得在ImageLoader中存在各种if-else判断,通过这些判断来确定使用哪种缓存。随着这些逻辑的引入,代码变得越来越复杂、脆弱,如果小民一不小心写错了某个if条件(条件太多,这是很容易出现的),那就需要更多的时间来排除。整个ImageLoader类也会变得越来越臃肿。最重要的是用户不能自己实现缓存注入到ImageLoader中,可扩展性可是框架的最重要特性之一。

于是通过抽象来达到程序的可扩展性,如图所示:


具体实现:
ImageLoader.java
public class ImageLoader {
    // 图片缓存
    ImageCache mImageCache = new MemoryCache();
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());
 
    // 注入缓存实现
    public void setImageCache(ImageCache cache) {
        mImageCache = cache;
    }
 
    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }
        // 图片没缓存,提交到线程池中下载图片
        submitLoadRequest(imageUrl, imageView);
    }
 
    private void submitLoadRequest(final String imageUrl,
             final 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);
            final HttpURLConnection conn = (HttpURLConnection) 
                        url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        } catch (Exception e) {
              e.printStackTrace();
        }
 
        return bitmap;
    }
}

ImageCache.java
public interface ImageCache {
    public Bitmap get(String url);
    public void put(String url, Bitmap bmp);
}
// 内存缓存MemoryCache类
public class MemoryCache implements ImageCache {
    private LruCache<String, Bitmap> mMemeryCache;
 
    public MemoryCache() {
        // 初始化LRU缓存
    }
 
     @Override
    public Bitmap get(String url) {
        return mMemeryCache.get(url);
    }
 
    @Override
    public void put(String url, Bitmap bmp) {
        mMemeryCache.put(url, bmp);
    }
}
 
// SD卡缓存DiskCache类
public  class  DiskCache implements ImageCache {
    @Override
    public Bitmap get(String url) {
        return null/* 从本地文件中获取该图片 */;
    }
 
    @Override
    public void put(String url, Bitmap bmp) {
        // 将Bitmap写入文件中
    }
}
 
// 双缓存DoubleCache类
public class DoubleCache implements ImageCache{
    ImageCache mMemoryCache = new MemoryCache();
    ImageCache mDiskCache = new DiskCache();
 
    // 先从内存缓存中获取图片,如果没有,再从SD卡中获取
    public Bitmap get(String url) {
       Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap == null) {
            bitmap = mDiskCache.get(url);
       }
        return bitmap;
     }
 
    // 将图片缓存到内存和SD卡中
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}
 

ImageLoader类中增加了一个setImageCache(ImageCache cache)函数,用户可以通过该函数设置缓存实现,也就是通常说的依赖注入。下面就看看是如何设置缓存实现的:
ImageLoader imageLoader = new ImageLoader() ;
        // 使用内存缓存
imageLoader.setImageCache(new MemoryCache());
        // 使用SD卡缓存
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(ImageCache cache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高。MemoryCache、DiskCache、DoubleCache缓存图片的具体实现完全不一样,但是,它们的一个特点是都实现了ImageCache接口。当用户需要自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,然后构造该类的对象,并且通过setImageCache(ImageCache cache)注入到ImageLoader中,这样ImageLoader就实现了变化万千的缓存策略,而扩展这些缓存策略并不会导致ImageLoader类的修改。

这就是开闭原则,虽然这些漂亮代码不是自己写的,但是受益匪浅,值得分享。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章