结合Android源码和图片加载的例子,介绍设计模式的同时,在例子中实际运用,更易于读者对设计模式的理解和使用。
本篇博客结合书中图片加载的例子和自己对知识点的理解,侧重记录下设计模式的使用,原理部分略过。
第一章 走向灵活软件之路——面向对象的六大原则
1.1 优化代码的第一步——单一职责原则
个人理解:“核心思想就是类的抽象和封装,将相同功能的代码封装在一个类中,不同功能的相互调用通过引用类对象方式实现"
下面引用书中的例子,对比下
这个是书中的第一个版本,相信大家能发现很多问题,比如每次都创建线程池和缓存,这些后面会进行优化,这块要说的是缓存的处理是一个独立的功能,应该抽象封装到一个类中。同样,下载功能也是如此。
public class ImageLoader {
private final int BYTE_UNIT = 1024;
// 图片缓存
private LruCache<String, Bitmap> mImageCache;
// 线程池,线程数量为CPU的数量
private ExecutorService mExecutorService;
public ImageLoader() {
initImageCache();
mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
private void initImageCache() {
// 计算可使用的最大内存
final long maxMemory = Runtime.getRuntime().maxMemory() / BYTE_UNIT;
final int memorySize = (int) maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(memorySize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// API19
return value.getAllocationByteCount() / BYTE_UNIT;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
// API12
return value.getByteCount() / 1024;
}
// Bitmap所占用的内存空间数等于Bitmap的每一行所占用的空间数乘以Bitmap的行数
return value.getRowBytes() * value.getHeight() / BYTE_UNIT;
}
};
}
/**
* 加载图片
* @param url
* @param imageView
*/
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);
}
});
}
private Bitmap downloadImage(String url) {
Bitmap bitmap = null;
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(url).openConnection();
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return bitmap;
}
}
书中在本节只对缓存功能做了封装,我们来看下封装后的代码。
public class ImageCache {
private final int BYTE_UNIT = 1024;
// 图片缓存
private LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
private void initImageCache() {
// 计算可使用的最大内存
final long maxMemory = Runtime.getRuntime().maxMemory() / BYTE_UNIT;
final int memorySize = (int) maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(memorySize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// API19
return value.getAllocationByteCount() / BYTE_UNIT;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR_MR1) {
// API12
return value.getByteCount() / 1024;
}
// Bitmap所占用的内存空间数等于Bitmap的每一行所占用的空间数乘以Bitmap的行数
return value.getRowBytes() * value.getHeight() / BYTE_UNIT;
}
};
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
}
public class ImageLoader {
// 封装的缓存类
private ImageCache mImageCache = new ImageCache();
// 线程池,线程数量为CPU的数量
private ExecutorService mExecutorService;
public ImageLoader() {
mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
/**
* 加载图片
* @param url
* @param imageView
*/
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);
}
});
}
private Bitmap downloadImage(String url) {
...................
}
}
1.2 让程序更稳定、更灵活——开闭原则
个人理解:“就是在设计类时,尽量考虑其扩展性,保证在有需求变更时,可以不修改原有代码。一般是通过面向接口,通过多态方式实现”。
软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开放——关闭原则。也就是说,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
上个例子中,图片缓存我们使用的ImageCache,主要是内存缓存。
这种问题就是,如果使用者想添加磁盘缓存,则需要更改现有代码,不易于扩展。
改成下面的方式,通过定义接口,调用setImageCache方式实现开发者扩展。
public interface ImageCache {
void put(String key, Bitmap bitmap);
Bitmap get(String key);
}
public class MemoryCache implements ImageCache {
// 图片缓存
private LruCache<String, Bitmap> mImageCache;
..............
@Override
public Bitmap get(String url) {
return mImageCache.get(url);
}
@Override
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
}
public class DiskCache implements ImageCache {
@Override
public void put(String key, Bitmap bitmap) {
}
@Override
public Bitmap get(String key) {
return null;
}
}
public class ImageLoader {
// 封装的缓存类
private ImageCache mImageCache = new MemoryCache();
// 线程池,线程数量为CPU的数量
private ExecutorService mExecutorService;
public ImageLoader() {
mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
}
public void setImageCache(ImageCache cache) {
this.mImageCache = cache;
}
......................
}
1.3 构建扩展性更好的系统——里氏替换原则
咱们直接来看第二种定义:所有引用基类的地方必须能透明的使用其子类的对象。
面向对象的语言的三大特点是继承、封装、多态,里氏替换原则就是依赖于继承、多态这两大特性。
1.4 让项目拥有变化的能力——依赖倒置原则
Java语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。其实就是面向接口/抽象编程。
1.5 系统有更高的灵活性——接口隔离原则
定义是:客户端不应该依赖它不需要的接口。接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。
1.6 更好的可扩展性——迪米特原则
通俗的讲,一个类应该对自己需要耦合或调用的类知道的最少,类的内部如何实现与调用者没关系,调用者只需要知道它需要的方法即可。
个人理解:“这个说的就是封装,尽量将相同的功能封装在一个类,只对外暴露需要的接口,其他的都不可见”。
终于整理完了第一章,这章说的核心思想就是面向对象的三大特点,继承、封装、多态。面向接口编程。