一、单一职责原则(Single Responsibility Principle 优化代码第一步)
就一个类而言,应该仅有一个引起他变化的原因。
需求:要实现图片加载+图片缓存的图片加载器(通过此demo1介绍两原则,望最少大致了解)
public class ImageLoader {
//图片缓存
LruCache<String, Bitmap> mImageCache;
//线程池,线程数量为CPU数量。
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI handler
Handler mUiHandler = new Handler(Looper.getMainLooper());
public ImageLoader(){
initImageCache();
}
private void initImageCache() {
//计算可用最大内存
final int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
//取1/4内存作为缓存
final int cacheSize = maxMemory/4;
mImageCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
// return super.sizeOf(key, value);
return value.getRowBytes()*value.getHeight()/1024;
}
};
}
public void displayImage(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);
}
});
}
private void updateImageView(final ImageView imageView, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
private 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;
}
}
demo1 缺点:ImageLoader耦合严重、无灵活性、扩展性,所有功能都在一个类 随着功能增多,类越来越大,代码越来越复杂,图片加载系统越来越脆弱、
为了满足单一职责原则,UML图如下,修改为demo2:
ImageLoader.java
public class ImageLoader {
//图片缓存
ImageCache mImageCache =new ImageCache();
//线程池,线程数量为CPU数量。
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI handler
Handler mUiHandler = new Handler(Looper.getMainLooper());
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)){
updateImageView(imageView,bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
private void updateImageView(final ImageView imageView, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
private 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 {
//图片URL缓存
LruCache<String, Bitmap> 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<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
// return super.sizeOf(key, value);
return value.getRowBytes()*value.getHeight()/1024;
}
};
}
public void put(String url,Bitmap bitmap){
mImageCache.put(url,bitmap);
}
public Bitmap get(String url){
return mImageCache.get(url);
}
}
如demo2,两个完全不一样的功能就不应该在一个类中。
优点:将ImageLoader一分为二,ImageLoader只负责加载图片,ImageCache 只负责处理图片缓存,职责清晰了。
缺点:欠缺扩展性
二、开闭原则(Open Close Principle 让程序更稳定、更灵活)
软件中的对象(类、模块、函数等)应该对于扩展是开放的,对于修改是封闭的。
分析Demo2,通过内存缓存解决了网络加载图片问题,但android内存有限,而且重启后要重新下载,又会导致变慢、浪费流量。现考虑将图片缓存到本地,优先使用内存缓存,内存中没有再使用SD卡缓存,也没有再从网络获取demo3如下:
添加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 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();
}
}
}
}
}
双缓存类DoubleCache.java
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 bitmap){
mMemoryCache.put(url,bitmap);
mDiskCache.put(url,bitmap);
}
}
ImageLoader .java 修改如下
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());
//UI handler
Handler mUiHandler = new Handler(Looper.getMainLooper());
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;
}
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);
}
});
}
public void useDiskCache(boolean useDiskCache){
isUseDiskCache = useDiskCache;
}
public void useDoubleCache(boolean useDoubleCache){
isUseDoubleCache = useDoubleCache;
}
private void updateImageView(final ImageView imageView, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
private 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;
}
}
缺点:每次加入新缓存都要修改原来代码,用户也不能自定义缓存,扩展性差;通过变量判断哪种缓存,造成if-else语句过多,ImageLoader类臃肿
分析利用开闭原则后的 demo4,UML图、代码如下:
ImageCache .java
public interface ImageCache {
public Bitmap get(String url);
public void put(String url,Bitmap bitmap);
}
MemoryCache .java
public class MemoryCache implements ImageCache{
//图片URL缓存
LruCache<String, Bitmap> mMemoryCache;
public MemoryCache() {
initImageCache();
}
private void initImageCache() {
//计算可用最大内存
final int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
//取1/4内存作为缓存
final int cacheSize = maxMemory/4;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
// return super.sizeOf(key, value);
return value.getRowBytes()*value.getHeight()/1024;
}
};
}
@Override
public Bitmap get(String url) {
return mMemoryCache.get(url);
}
@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url,bitmap);
}
}
DiskCache .java
public class DiskCache implements ImageCache{
//接口方法,内容不变
@Override....
@Override....
}
DoubleCache .java
public class DoubleCache implements ImageCache{
ImageCache mMemoryCache = new MemoryCache();
//接口方法,内容不变
@Override....
@Override....
}
ImageLoader .java
public class ImageLoader {
//内存缓存
ImageCache mImageCache = new MemoryCache();
//线程池,线程数量为CPU数量。
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//UI handler
Handler mUiHandler = new Handler(Looper.getMainLooper());
//注入缓存实现
public void setImageCache(ImageCache imageCache){
mImageCache = imageCache;
}
public void displayImage(final String url, final ImageView imageView){
Bitmap bitmap = mImageCache.get(url);
if(bitmap != null){
imageView.setImageBitmap(bitmap);
return;
}
//图片没有缓存。提交到线程池中下载图片
submitLoadRequest(url,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);
}
}
});
}
private void updateImageView(final ImageView imageView, final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
private 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;
}
}
通过ImageLoader 里的 setImageCache(ImageCache imageCache)方法注入不同的缓存实现。使得ImageLoader 简单健壮、可扩展性灵活性更高。当用户需要自定义实现缓存策略时,只需要实现ImageCache接口并调用set方法即可。即实现可开闭原则(重要手段是通过抽象)
三、里氏替换原则(Liskov Substitution Principle 构建扩展性更好的系统)
所有引用基类的地方必须能够透明地使用其子类对象。(即只要父类能够出现的地方,子类就可以出现,而且替换成子类也不会产生任何错误或异常)总结为两个字:抽象
结合上面的ImageLoader,用户只需要在使用时指定具体的缓存对象,就可以动态的替换ImageLoader中的缓存策略。保证了可扩展型。
四、依赖倒置原则(Dependence Inversion Principle 让项目拥有变化的能力)
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的。(即面向 接口/抽象 编程)
之前相关代码:
public interface ImageCache {
public Bitmap get(String url);
public void put(String url,Bitmap bitmap);
}
public class ImageLoader {
//图片缓存类,依赖于抽象,并有一个默认实现
ImageCache mImageCache = new MemoryCache();
//略
//设置缓存策略,依赖于抽象
public void setImageCache(ImageCache imageCache){
mImageCache = imageCache;
}
//略
}
我们建立了ImageCache 抽象,并让ImageLoader 依赖于抽象而不是具体细节。当需求变化时,只需要实现ImageCache 或继承ImageCache 子类,然后注入到ImageLoader 即可替换。保证了高扩展性,有了拥抱变化的能力。
我们发现想让系统更加灵活,抽象似乎成了我们唯一的手段。
五、接口隔离原则(Interface Segregation Principle 系统有更高的灵活性)
客户端不应该依赖他不需要的接口(类间的依赖关系应该建立在最小的接口上)
demo:OutputStream一般的写法为:
public void put(String url,Bitmap bmp){
FileOutputStream fileOutputStream =null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
}catch (FileNotFoundException e){
e.printStackTrace();
}finally {
if(fileOutputStream!=null){
try{
fileOutputStream.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
这段可读性非常差,我们可以利用Closeable接口,他标识一个可关闭对象,只有一个close方法,统一关闭对象。我们定义一个工具CloseUtils
public final class CloseUtils{
private CloseUtils(){}
public static void closeQuietly(Closeable closeable){
if(null!=closeable){
try{
closeable.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
运用后最终如下:
public void put(String url,Bitmap bmp){
FileOutputStream fileOutputStream =null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
}catch (FileNotFoundException e){
e.printStackTrace();
}finally {
CloseUtils.closeQuietly(fileOutputStream);
}
}
代码简洁了很多, CloseUtils建立在最小化依赖原则的基础上,只需要知道这个对象是可关闭的,其他一概不关心,也就是接口隔离原则。
六、迪米特原则(Law of Demeter 更好的扩展性)
也称最少知识原则:一个类应该对其他对象有最少的了解(一个类应该对自己需要耦合或调用的类知道最少,类的内部如何实现与调用者或者依赖者没有关系,调用者或者依赖者只需要知道他需要的方法即可,其他一律不管)
用上面的ImageCache 来说,用户完全不知道DiskLruCache的存在,只需要与ImageCache 打交道即可。ImageCache 将一切细节隐藏在了直接“朋友”的外衣下,使得系统具有更低的耦合性和更好的可扩展型。
注,本节ImageCache 知识阐释原理的demo,不建议直接使用到项目中。