转载请注明出处:http://blog.csdn.net/binbinqq86/article/details/70949203
在软件开发的过程中,最难的不是完成业务需求的开发工作,而是在后续的升级,维护过程中让软件系统能够拥抱变化。拥抱变化也就意味着在满足业务需求且不破坏系统稳定性的前提下保持高可扩展性、高内聚、低耦合,在经历了各版本的变更之后依然保持清晰、灵活、稳定的系统架构。当然,这是一个比较理想的情况,但我们必须要朝着这个方向去努力,而且这些东西随着你软件开发年限的增加,你会越来越深有体会。下面我将从一个小例子去逐步说明应该怎么去优化代码。
图片加载相信现在基本上大家都是用的第三方框架,比如谷歌volley,glide,universalimageloader,xutils,Picasso,fresco等等等等,太多太多了,但是你真的了解其中的原理吗,作为一名合格的程序猿,我们必须懂其中的实现原理,于是乎我的朋友小明就想自己去亲手实现一个,一方面可以帮助自己深入理解,另外一方面也可以锻炼自己的技能。
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.widget.ImageView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by tb on 2017/4/30.
* 图片加载类
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
//图片缓存
LruCache<String,Bitmap> mImageCache;
//线程池,线程数量为cpu的数量
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public ImageLoader(){
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 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)){
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
很简单的一段代码,下载图片并存入缓存。写完之后小明当然很高兴了,就给他的主管看了看,并且放到了github上进行开源,想要得到一种认可,主管看了之后就把他叫去了,说这个ImageLoader耦合太严重啦!所有的功能都写在一个类里怎么行呢,这样如果想后期扩展维护,这个类也会越来越大,代码也越来越复杂,整个图片加载系统就会越来越脆弱。。。你还是拆分一下,把各个功能独立出来,让他们满足单一职责原则。小明捕捉到了单一职责原则这个关键词,他用谷歌搜索了一些资料之后,总算明白了一些,于是打算对自己的代码进行一次重构。
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.widget.ImageView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by tb on 2017/4/30.
* 图片加载类
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
//图片缓存
ImageCache mImageCache=new ImageCache();
//线程池,线程数量为cpu的数量
ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
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)){
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by tb on 2017/4/30.
*/
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 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);
}
}
ImageLoader被一分为二,一个只负责图片加载的逻辑,而另外一个ImageCahce只负责缓存工作,这样当图片加载逻辑需要改变时,不会影响缓存逻辑,而需要改变缓存策略时也不影响加载逻辑,整个代码结构变的清晰了许多。这就是单一职责所带来的好处:各司其职。
小明的重构获得了主管的肯定,但是仍然不是很满意,就让他回去继续思考,并且github上有人反应缓存系统不够完善,每次退出应用都要重新下载图片,导致加载缓慢又耗流量。偶然的一次,小明看到了开闭原则这个名词,是这样定义的:软件中的对象应该对于扩展是开放的,但是,对于修改是封闭的。在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会将错误引入原本已经经过测试的旧代码中,破坏原有系统。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。当然,在现实的开发中,只通过继承的方式来升级、维护原有系统只是一个理想化的情况,因为,在实际开发中,修改原有代码、扩展代码往往是同时存在的。
看到这里,小明似乎又有了新的思路:
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.widget.ImageView;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by tb on 2017/4/30.
* 图片加载类
*/
public class ImageLoader {
private static final String TAG = "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 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)){
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by tb on 2017/4/30.
*/
public interface ImageCache {
public void put(String url,Bitmap bitmap);
public Bitmap get(String url);
}
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by tb on 2017/4/30.
* //内存缓存MemoryCache类
*/
public class MemoryCache implements ImageCache{
private LruCache<String,Bitmap> mMemoryCache;
public MemoryCache(){
//初始化Lru缓存
}
@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url,bitmap);
}
@Override
public Bitmap get(String url) {
return mMemoryCache.get(url);
}
}
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by tb on 2017/4/30.
* //SD卡缓存DiskCache类
*/
public class DiskCache implements ImageCache{
@Override
public void put(String url, Bitmap bitmap) {
//将bitmap写入文件中
}
@Override
public Bitmap get(String url) {
return null;//从本地文件中获取该图片
}
}
package com.example.tb.myapplication;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by tb on 2017/4/30.
* //双缓存DoubleCache类
*/
public class DoubleCache implements ImageCache{
private ImageCache mMemoryCache=new MemoryCache();
private ImageCache mDiskCache=new DiskCache();
//将图片缓存到内存和sd卡中
@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增加了一个setImageCache函数,用户可以通过该函数设置缓存实现,也就是通常说的依赖注入。这次小明把代码给主管看,得到了表扬,通过接口实现了丰富的扩展,让用户可以随意增加缓存策略,而不必修改ImageLoader类。主管又给小明讲了里氏替换原则:其实说白了就是抽象,把缓存逻辑单独抽象出来,这样具体的实现交给其子类去完成,可以实现千变万化的缓存策略。
软件优化的另外一个原则:依赖倒置原则。
- 高层模块不应该依赖底层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
在java中,抽象就是指接口或者抽象类,两者都是不能直接被实例化的,细节就是实现类,这个原则的核心思想其实也是抽象。案例中通过setImageCache函数实现缓存功能的替换,这就保证了缓存系统的高扩展性,有了拥抱变化的能力,这就是依赖倒置原则。
第五个原则:接口隔离原则。即客户端不应该依赖它不需要的接口。
//将图片缓存到内存和sd卡中
@Override
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream=null;
try {
fileOutputStream=new FileOutputStream(cacheDir+url);
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(fileOutputStream!=null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我们看到这段代码可读性非常差,各种try…catch,我们知道java中有一个Closeable接口,标识了一个可关闭对象,于是小明打算建一个统一的方法来关闭这些对象:
/**
* Created by tb on 2017/4/30.
*/
public class CLoseUtils {
private CLoseUtils(){
}
public static void closeQuietly(Closeable closeable){
if(closeable!=null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//将图片缓存到内存和sd卡中
@Override
public void put(String url, Bitmap bitmap) {
FileOutputStream fileOutputStream=null;
try {
fileOutputStream=new FileOutputStream(cacheDir+url);
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
CLoseUtils.closeQuietly(fileOutputStream);
}
}
我们可以看到加入之后,代码简洁了很多,并且后续有其他对象需要关闭的时候可以直接简单的调用,非常方便。这个closeQuietly方法可以运用到各类可关闭的对象中,保证了代码的重用性。ClostUtils的closeQuietly的基本原理就是依赖于Closeable抽象而不是具体实现,并且建立在最小化依赖原则的基础上,它只需要知道这个对象是可关闭的,其他的一概不关心,这就是接口隔离原则。
最后一个原则:迪米特原则。即一个对象应该对其他对象有最少的了解。通俗的说就是调用者只需要知道另外一个类中的方法即可,不需要知道具体实现,就比如小明写的开源项目,用户只需要跟ImageCache打交道就行了,具体的缓存put方法里面的实现,用户不需要关心,无论是jakeWharton的DiskLruCache的实现还是自己写的普通文件读取,用户根本感知不到,可以随意替换,这就达到了更低的耦合和更好的扩展。
当然软件优化的方法还有很多很多,遵循这里提出的六大原则只是我们迈出的第一步。