您的位置:首页 > 移动开发 > Android开发

六大设计原则之开闭原则

2017-08-12 14:37 92 查看
概念

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

封闭的。简单说就是,对于经过测试已经稳定的代码禁止修改,如果有新的需求只允许扩展原有代

码。因为修改可能将错误引入到已经经过测试稳定的代码,破坏原有系统。当然这只是理想中的状

态,在实际开发中,修改原有代码和扩展代码都同时存在的,我们要做的就是不断的去接近这种理

想的状态。开闭原则是面向对象编程世界里最基础的设计原则,它指导我们如何构建一个稳定的、

灵活的系统。

那么我们怎么扩展代码,怎么封闭代码?还是用上一篇文章中的例子来说明。

在单一职责里面的例子(六大设计原则之单一职责),似乎是完美的,通过内存缓存解决了每次都

从网络加载图片的问题,但是当内存被回收或者应用重启,原来加载过的图片就消失,需要重新加

载,这样耗流量耗时间,体验很差。于是,现在提出了新的缓存策略:用SD卡缓存。文章有点长,

代码有点多,是为了引出问题,然后解决问题,总之专研技术需要耐心,最好自己敲一遍代码,体

会其中的不同,通过层层优化,最后达到较理想的效果。

优化之前的代码逻辑

sd卡缓存:

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* Created by WKC on 2017/8/8.
*/

public class DiskCache {
//路径
private 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的代码,具体代码如下

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 图片加载类
* Created by WKC on 2017/7/8.
*/

public class ImageLoader {
private Bitmap bitmap;
//是否使用SD卡缓存
private boolean isUseDiskCache = false;
//内存缓存
private ImageCache mImageCache = new ImageCache();
//SD卡缓存
private DiskCache mDiskCache = new DiskCache();

//线程池,线程数量为CUP的数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
availableProcessors());
//UI Handler
Handler mUiHandler = new Handler(Looper.getMainLooper());

/**
* 提供外部调用显示图片的函数
*
* @param url       图片的路径
* @param imageview 显示图片的view
*/
public void displayImage(final String url, final ImageView imageview) {
bitmap = getUseDiskCache() ? 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)) {
updateImageview(imageview, bitmap);
}
mImageCache.put(url, bitmap);
}
});
}

/**
* 在UI线程显示图片
*
* @param imageview
* @param bitmap
*/
private void updateImageview(final ImageView imageview, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageview.setImageBitmap(bitmap);
}
});
}

/**
* 下载图片
*
* @param imegeUrl
* @return
*/
private Bitmap downloadImage(String imegeUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imegeUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}

public boolean getUseDiskCache() {
return isUseDiskCache;
}

public void setUseDiskCache(bool
1014e
ean useDiskCache) {
isUseDiskCache = useDiskCache;
}
}


通过以上代码,仅增加了DiskCache类和往imageLoader类中加入少量的代码就增加了SD卡缓存的

功能,且我们可以直接设置useDiskCache的值来决定是否使用SD卡缓存,很是方便。但是聪明的

你一定发现了上面的例子中,当用户使用了内存就不能用SD卡缓存,反之亦然。能不能再优化下,

两种缓存策略都用,即优先使用内存缓存,如果没有图片再使用SD卡缓存,如果SD卡中也没有图片

最后才从网络中获取。于是,我们新建一个双缓存类:

双缓存类

package com.study.demo.designdemo;

import android.graphics.Bitmap;

/**
* Created by WKC on 2017/7/8.
*/

public class DoubleCache {
//内存
ImageCache mMemoryCache = new ImageCache();
//SD卡
DiskCache mDiskCache = new DiskCache();

//先从内存中获取图片,如果没有,再从SD卡中获取
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}

//将图片缓存
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}


同样,ImageLoader类也需要更新,代码如下

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 图片加载类
* Created by WKC on 2017/7/8.
*/

public class ImageLoader {
private Bitmap bitmap;

//内存缓存
private ImageCache mImageCache = new ImageCache();
//SD卡缓存
private DiskCache mDiskCache = new DiskCache();
//双缓存
private DoubleCache mDoubleCache = new DoubleCache();
//是否使用SD卡缓存
private boolean isUseDiskCache = false;
//是否使用双缓存
private boolean isDoubleCache = false;

//线程池,线程数量为CUP的数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
availableProcessors());
//UI Handler
Handler mUiHandler = new Handler(Looper.getMainLooper());

/**
* 提供外部调用显示图片的函数
*
* @param url       图片的路径
* @param imageview 显示图片的view
*/
public void displayImage(final String url, final ImageView imageview) {
if (getDoubleCache()) {
bitmap = mDoubleCache.get(url);
} else if (getUseDiskCache()) {
bitmap = mDiskCache.get(url);
} else {
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)) {
updateImageview(imageview, bitmap);
}
mImageCache.put(url, bitmap);
}
});
}

/**
* 在UI线程显示图片
*
* @param imageview
* @param bitmap
*/
private void updateImageview(final ImageView imageview, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageview.setImageBitmap(bitmap);
}
});
}

/**
* 下载图片
*
* @param imegeUrl
* @return
*/
private Bitmap downloadImage(String imegeUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imegeUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}

public boolean getUseDiskCache() {
return isUseDiskCache;
}

public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}

public boolean getDoubleCache() {
return isDoubleCache;
}

public void setDoubleCache(boolean doubleCache) {
isDoubleCache = doubleCache;
}
}


有没有发现,每当我们新增加一个需求,我们所有的类都需要改动,麻烦不说,万一有其他类正在

调用原有的代码,是不是可能会惹来“麻烦”?而且使用了很多的判断逻辑导致代码很复杂,这是

很容易出现bug的,而且是不容易找出.再者,用户不能自己实现/定义缓存注入到ImageLoader中,可

扩展性差,背离了面向对象的思想。

“软件中的对象(类。模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开

放关闭原则。”也就是说当需求变化时,我们应该尽量通过扩展方式来实现变化,而不是通过修改

已有的代码来实现。为了实现这样的效果,我们需要对前面的代码来一次重构,对上面的代码重构

中涉及的几个类的代码都发生了不同程度的改变,所以我打算把所有的各类的代码都贴出来。

优化之后的代码逻辑

我们先看下UML图,对UML图不了解的这篇文章深入浅出UML类图对你有帮助。



代码中的注释已经很详细了,就不在详细解释。

1.ImageLoader类

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 图片加载类
* Created by WKC on 2017/7/8.
*/

public class ImageLoader {
//默认的缓存方式
private ImageCache mImageCache = new MemoryCache();

//线程池,线程数量为CUP的数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().
availableProcessors());
//UI Handler
Handler mUiHandler = new Handler(Looper.getMainLooper());

/**
* 在UI线程显示图片
*
* @param imageview
* @param bitmap
*/
private void updateImageview(final ImageView imageview, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageview.setImageBitmap(bitmap);
}
});
}

/**
* 注入缓存实现
*
* @param cache
*/
public void setImageCache(ImageCache cache) {
mImageCache = cache;
}

/**
* 提供外部调用显示图片的函数
*
* @param url       图片的路径
* @param imageview 显示图片的view
*/
public void displayImage(final String url, final ImageView imageview) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageview.setImageBitmap(bitmap);
return;
}

submitLoadRequest(url, imageview);
}

/**
* 没有缓存,则从网络中获取
*
* @param url
* @param imageview
*/
private void submitLoadRequest(final String url, final ImageView imageview) {
imageview.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (imageview.getTag().equals(url)) {
updateImageview(imageview, bitmap);
}
mImageCache.put(url, bitmap);
}
});
}

/**
* 下载图片
*
* @param imegeUrl
* @return
*/
private Bitmap downloadImage(String imegeUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imegeUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}


经过这次重构,没有了那么多的 if-else 语句了,没有了各种各样的缓存实现对象、布尔变量,代码

也清晰简单了很多。不过这里用到的ImageCache 已经不是之前的那个ImageCache类了,而是把

他提取成图片缓存接口,抽象缓存的功能。我们来看看该接口中的声明:

2.图片缓存接口

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

Bitmap get(String url);
}


该接口定义了获取和缓存图片的两个函数,缓存的key是图片的url,值是图片本身。然后让内存缓

存、SD卡缓存或者

双缓存都实现该接口。

3.内存缓存

package com.study.demo.designdemo;
import android.graphics.Bitmap;
import android.util.LruCache;

/**
* Created by WKC on 2017/7/8.
*/

public class MemoryCache implements ImageCache {
private LruCache<String, Bitmap> mMemoryCache;

public MemoryCache() {
//初始化缓存
initImageCache();
}

@Override
public Bitmap get(String url) {
return mMemoryCache.get(url);
}

@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
}

//初始化缓存内存的参数
private void initImageCache() {
//计算可使用的最大内存
final long maxMemory = Runtime.getRuntime().maxMemory() / 1024;
//取四分之一的可用内存作为缓存
final int cacheSize = (int) (maxMemory / 4);
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
}


4.SD卡缓存

package com.study.demo.designdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* Created by WKC on 2017/7/8.
*/

public class DiskCache implements ImageCache {
private static String cacheDir = "sdcard/cache/";
/**
* 从SD卡缓存中获取图片
* @param url
* @return
*/
@Override
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}

@Override
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream = null;
try {
//从内存中写入SD卡
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();
}
}
}
}
}


5.双缓存

package com.study.demo.designdemo;

import android.graphics.Bitmap;

/**
* Created by WKC on 2017/7/8.
*/

public class DoubleCache implements ImageCache {
//内存
private ImageCache mMemoryCache = new MemoryCache();
//SD卡
private ImageCache mDiskCache = new DiskCache();

@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}

@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}


6.最后是调用

细心的读者可能注意到了,在ImageLoader中增加了SetImageCache(ImageCache cache)函

数,用户可以通过该函数设置缓存实现,不再需要去更改Imageloader函数中的代码了,也就

是常说的依赖注入。这样也就达到了我们的最初目的。

我们来看下用户如何设置缓存的

imageView = (ImageView) findViewById(R.id.imageView);
//使用内存缓存策略
imageLoader.setImageCache(new MemoryCache());
//使用SD卡缓存策略
imageLoader.setImageCache(new DiskCache());
//使用双缓存策略
imageLoader.setImageCache(new DoubleCache());
//使用自定义的缓存
imageLoader.setImageCache(new ImageCache() {
@Override
public Bitmap get(String url) {
//获取图片,返回Bitmap对象
return null;
}

@Override
public void put(String url, Bitmap bitmap) {
//                缓存图片
}
});
imageLoader.displayImage(url,imageView);


最后来下总结:

1.需求的变更

内存缓存实现图片缓存–>磁盘缓存实现图片缓存–>内存和磁盘的双缓存实现图片缓存。

2.如何优化

从开始的每次需求的变更都需要去更改原先的代码,而且代码中有了不少的逻辑判断,容易

出现失误。–>我们定义了一个规则(ImageCache接口),当我们的需求变化时,只需要更改

实现这个接口,并把它注入缓存类(ImageLoader类),就能在不改变原有代码的条件下实

现了我们新的需求,也没有了那么多的逻辑判断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息