您的位置:首页 > 其它

面向对象六大原则--开闭原则

2016-07-13 16:32 387 查看
在上一篇提到了单一职责原则,如果没有阅读的小伙伴可以先去看下那篇单一职责原则

接下来在这篇中介绍开闭原则,还是以ImageLoader图片加载为例说明

一、OCP定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。

上篇提到的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 bmp){
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir+url);
bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
}catch (Exception e){
e.printStackTrace();
}finally {
if(fileOutputStream!=null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}


相应的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;
}
//没有缓存,则提交给线程下载
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;
}
public void userDiskCache(boolean useDiskCache){
isUseDiskCache = useDiskCache;
}
}


大家可以看到仅仅新增了一个SD卡缓存,就修改了很多代码,这样的代码其实很不坚强,如果在继续增加一个双缓存,也得进行修改ImageLoader并且新增一个类,这样弄起来很不方便。一般懒人都会有懒人的方法,这样写也不符合设计的原则,不灵活,扩展性也不好。

接下来我们很有必要去认真思考下,并且重构下代码,大家可以观察看到所有新增类都会去获取缓存中图片和放入对象的图片,那我们就可以把他们抽取出来,定义一个接口,代码如下:

public interface ImageCache {
public void put(String url, Bitmap bitmap);
public Bitmap get(String url);
}


然后重构内存缓存代码,sd卡缓存,双缓存

内存缓存:

public class MemoryCache implements ImageCache {

//图片缓存
LruCache<String,Bitmap> mMemeryCache;

public MemoryCache(){
//初始化LRU缓存
initImageCache();
}
private void initImageCache() {
//计算可使用的最大内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//取四分之一的可用内存作为缓存
final int cacheSize = maxMemory / 4;
mMemeryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.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 Bitmap get(String url){
return BitmapFactory.decodeFile(cacheDir+url);
}
//将图片缓存到内存卡中
@Override
public void put(String url, Bitmap bmp){
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir+url);
bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
}catch (Exception e){
e.printStackTrace();
}finally {
if(fileOutputStream!=null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}


双缓存

public class DoubleCache implements ImageCache {

ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();

@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url,bitmap);
mDiskCache.put(url,bitmap);
}
//先从内存缓存中获取图片,如果没有,再从SD卡中获取
@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();
//线程池,线程数量为CPU的数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

public void setImageCache(ImageCache cache){
mImageCache = cache;
}
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并不是原来的ImageCache,并且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 bitmap) {
//缓存图片

}
//从缓存中获取图片
@Override
public Bitmap get(String url) {
return null;
}
});


大家是不是发现了这样写可以使得代码更简单,健壮,扩展性比之前要好很多,也更加灵活了。

其实在学习,编写代码的时候最好要多思考,开闭原则知道我们,当软件需要变化时,应尽量通过扩展的方式来实现变化,而不是通过修改已有的代码实现。这里的“应该尽量”4个字说明OCP原则并不是说绝对不可以修改原始类的。

今天开闭原则就介绍到这里,希望大家理解它其中的精髓,运用起来。

下一篇文章将为大家介绍里氏替换原则和依赖倒置原则。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息