原文链接:http://www.apkbus.com/blog-705730-62838.html
列举出设计模式中的六大原则应该如何实现
单一职责原则 SRP
Single Responsibility Principle
场景: 如果让你写出一个图片缓存类, 要求内部实现缓存策略, 并提供方法只需要传递控件和图片地址就可以自动设置背景的类.
先给出最简单直接的写法.
public class ImageLoader { /** * 图片的缓存 */ LruCache<String , Bitmap> mImageCache; /** * 线程池 */ ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 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) { // 返回缓存的bitmap大小 return value.getRowBytes() * value.getHeight() /1024 ; } }; } public void displayImage(final ImageView iv, final String imgUrl){ // 获取缓存 Bitmap bitmap = mImageCache.get(imgUrl); if(null != bitmap){ iv.setImageBitmap(bitmap); return; } // 网络加载 iv.setTag(imgUrl); mExecutorService.submit(new Runnable() { @Override public void run() { Bitmap bitmap = downloadImage(imgUrl); if (null == bitmap) return ; if (iv.getTag().equals(imgUrl)) { iv.setImageBitmap(bitmap); } mImageCache.put(imgUrl,bitmap); } }); } /** * 根据图片的url下载图片并转换成bitmap对象返回 */ public Bitmap downloadImage(String url){ Bitmap bitmap = null; try { URL url1 = new URL(url); HttpURLConnection conn = (HttpURLConnection) url1.openConnection(); bitmap = BitmapFactory.decodeStream(conn.getInputStream()); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; } } ``` 对于功能来说这是没有问题的. 但是接下来分析一下. 这样写会有什么弊端. 1. 首先这个类的职责包括了两个`控件设置背景的逻辑`和`图片下载的逻辑`. 耦合性太强, 2. `类的职责过多`那么就会造成类中的代码更多,两个逻辑的代码会交叉分布. 不易被阅读. 3. `扩展性`随着后续的实现`磁盘缓存`修改`加载图片`逻辑等会让代码越来越复杂难懂. 那么重构一下, 让两个职责分离开来. ```java /** * 处理图片的加载 */ public class ImageLoader { /** * 图片的缓存 */ ImageCache mImageCache = new ImageCache(); /** * 线程池 */ ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public void displayImage(final ImageView iv, final String imgUrl){ // 获取缓存 Bitmap bitmap = mImageCache.getCache(imgUrl); if(null != bitmap){ iv.setImageBitmap(bitmap); return; } // 网络加载 iv.setTag(imgUrl); mExecutorService.submit(new Runnable() { @Override public void run() { Bitmap bitmap = downloadImage(imgUrl); if (null == bitmap) return ; if (iv.getTag().equals(imgUrl)) { iv.setImageBitmap(bitmap); } mImageCache.putCache(imgUrl,bitmap); } }); } /** * 根据图片的url下载图片并转换成bitmap对象返回 */ public Bitmap downloadImage(String url){ Bitmap bitmap = null; try { URL url1 = new URL(url); HttpURLConnection conn = (HttpURLConnection) url1.openConnection(); bitmap = BitmapFactory.decodeStream(conn.getInputStream()); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; } } /** * 图片缓存逻辑处理类 */ public class ImageCache { /** * 图片的缓存 */ 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) { // 返回缓存的bitmap大小 return value.getRowBytes() * value.getHeight() /1024 ; } }; } /** * 提供一个对 bitmap 进行缓存的方法 */ public void putCache(String imgUrl, Bitmap bitmap){ mImageCache.put(imgUrl, bitmap); } /** * 对外提供一个 获取缓存的方法 */ public Bitmap getCache(String imgUrl){ return mImageCache.get(imgUrl); } } |
将原类拆分为两个类之后, 每个类的职责变得清晰. 如果需要更改缓存策略那么只需要修改ImageCache类总逻辑. 如果需要替换HttpURLConnection为OKHttp那么只需要在ImageLoader类中修改. 这样充分了体现了一个类单一职责SRP的好处, 清晰, 排除了修改时的多余干扰代码.
开闭原则 OCP
全称Open Close Principle
定义: 对于对象应该对于扩展是开放的, 对于修改是封闭的. 就是在后续的功能添加的时候要尽可能做到不修改已存在的代码! 就是通过继承``接口的特性来实现的. 就是我们口中常说的面向接口编程
不多大理论, 代码中会有真相.
还是上面的代码, 这个时候我们如果想增添一个二级缓存,添加一个磁盘的缓存那么代码就如下了
/** * 磁盘缓存 */ public class DiskCache { public static final String cacheDir = "sdcard/cache/"; // 从磁盘中获取缓存 public Bitmap getCache(String imgUrl){ return BitmapFactory.decodeFile(cacheDir+imgUrl); } // 向磁盘中缓存 public void putCache(String imgUrl, Bitmap bitmap){ try(FileOutputStream fileOutputStream = new FileOutputStream(cacheDir+imgUrl)){ // 对bitmap压缩到本地文件 bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); } catch (IOException e) { e.printStackTrace(); } } } // 还需要对ImageLoader类进行部分修改 只贴出添加的代码 /** * 处理图片的加载 */ public class ImageLoader { /** * 磁盘缓存 */ DiskCache mDiskCache = new DiskCache(); /** * 设置一个标记表示是否开启磁盘缓存 */ public boolean isUseDiskCache = false;
public void displayImage(final ImageView iv, final String imgUrl){ // 获取缓存 Bitmap bitmap = mImageCache.getCache(imgUrl); if(null != bitmap){ iv.setImageBitmap(bitmap); return; } // 判断磁盘缓存 if (isUseDiskCache){ bitmap = mDiskCache.getCache(imgUrl); if (null != bitmap) return; }
// 省略相同的网络下载代码... } public void setUseDiskCache(boolean useDiskCache) { isUseDiskCache = useDiskCache; } } |
至此初步的添加功能需求已经完成, 增加了一个DiskCache类实现磁盘缓存, 并在ImageLoader类中进行判断使用的代码.
分析: 现在的加载可以有两种.一种是只使用LruCache缓存, 另一种是实现两种缓存同时实现. 那么这样的代码会有怎样的问题?
· 问题1: 如果想实现单磁盘缓存? 那么必须再对ImageLoader进行修改. 添加判断条件. 并且三种缓存策略if的判断也就比较多.
· 问题2: 如果用户想实现自定义缓存? 呵呵哒. 目前的代码没这么厉害的扩展性…
那么看一下UML类图
可以看出只要添加一个缓存策略就要建立一个依赖关系.
那么如果通过接口的方式来修改代码呢. 看一下…
/** * 缓存接口 */ public interface BaseCache { // 添加缓存的抽象方法 void putCache(String imgUrl, Bitmap bitmap); // 获取缓存的抽象方法 Bitmap getCache(String imgUrl); } /** * 双缓存 */ public class DoubleCache implements BaseCache { BaseCache mMemoryCache = new ImageCache(); BaseCache mDiskCache = new DiskCache(); @Override public void putCache(String imgUrl, Bitmap bitmap) { // 缓存 mMemoryCache.putCache(imgUrl, bitmap); mDiskCache.putCache(imgUrl, bitmap); } @Override public Bitmap getCache(String imgUrl) { // 先从缓存取没有再从sd取 Bitmap cache = mMemoryCache.getCache(imgUrl); if (null == cache){ cache = mDiskCache.getCache(imgUrl); } return cache; } } /** * 图片缓存逻辑处理类 */ public class ImageCache implements BaseCache{ /** * 图片的缓存 */ 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) { // 返回缓存的bitmap大小 return value.getRowBytes() * value.getHeight() /1024 ; } }; } /** * 提供一个对 bitmap 进行缓存的方法 */ @Override public void putCache(String imgUrl, Bitmap bitmap){ mImageCache.put(imgUrl, bitmap); } /** * 对外提供一个 获取缓存的方法 */ @Override public Bitmap getCache(String imgUrl){ return mImageCache.get(imgUrl); } } /** * 处理图片的加载 */ public class ImageLoader { /** * 图片的缓存 默认只是内存缓存 */ BaseCache mImageCache = new ImageCache(); /** * 注入缓存策略 */ public void setmImageCache(BaseCache mImageCache) { this.mImageCache = mImageCache; }
/** * 线程池 */ ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public void displayImage(final ImageView iv, final String imgUrl){ // 获取缓存 具体的缓存策略实现了依赖注入. 有调用者后续决定, 默认内存缓存 Bitmap bitmap = mImageCache.getCache(imgUrl); if(null != bitmap){ iv.setImageBitmap(bitmap); return; }
// 网络加载 iv.setTag(imgUrl); mExecutorService.submit(new Runnable() { @Override public void run() { Bitmap bitmap = downloadImage(imgUrl); if (null == bitmap) return ; if (iv.getTag().equals(imgUrl)) { iv.setImageBitmap(bitmap); } mImageCache.putCache(imgUrl,bitmap); } }); } /** * 根据图片的url下载图片并转换成bitmap对象返回 */ public Bitmap downloadImage(String url){ Bitmap bitmap = null; try { URL url1 = new URL(url); HttpURLConnection conn = (HttpURLConnection) url1.openConnection(); bitmap = BitmapFactory.decodeStream(conn.getInputStream()); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; } } |
上面代码没有单磁盘缓存的策略, 但是如果现在想要实现了这个功能, 怎么做? 只需要创建一个类实现BaseCache. 在创建ImageLoader的时候通过setmImageCache()来注入不同的实现. 不需要修改源代码, 并且ImageLoader中的if语句判断和布尔标记也完全不需要, 代码更加简洁.
看一下类图:
可以看到ImageLoader依赖了接口编程. 接口定义了缓存的共性方法. 在后续只要是其子类就可以使用.这不是满足了开闭原则的定义, 对修改封闭, 对于扩展开放了.
里氏替换原则 LSP
全称Liskov Substitution Principle
定义: 所有引用基类的地方必须能透明的使用其子类.
说白了就是Java中的继承和多态的特性. 父类可以直接引用子类类型. 比如Object可以引用任何类型的概念.
其实在上面的开闭原则中就已经存在了里氏替换
/** * 注入缓存策略 */ public void setmImageCache(BaseCache mImageCache) { this.mImageCache = mImageCache; } |
参数接收的BaseCache类型. 可以透明的引用任何的子类. 通过抽象实现了多种可能.
一般来说开闭原则和里氏替换是不离不弃, 生死相依的. 通过里氏替换达到了对扩展的开发, 对修改封闭的效果. 这两个原则同时实现了一个OOP的一个重要特性抽象
依赖倒置原则 DIP
全称:Dependence Inversion Principle
定义: 指代了一种特定的解耦方式, 是的高层次的模块不依赖于低层次的模块实现细节.
· 高层次模块不应该依赖低层次, 两者都应该依赖其抽象
· 抽象不应该依赖细节
· 细节应该依赖抽象
而在Java中的表现就是: 模块间的依赖通过抽象发生, 实现类之间不发生直接的依赖关系, 其依赖关系是通过接口或抽象产生的
直接看代码, 还是上面的ImageLoader类中
// 依赖于接口抽象, BaseCache mImageCache = new ImageCache(); // 依赖于细节, 内存缓存 ImageCache mImageCache = new ImageCache(); // 依赖于细节, 双缓存 DoubleCache mImageCache = new DoubleCache(); |
如果依赖了内存缓存细节, 那么注入的缓存策略必须是ImageCache的子类. 但是这个子类已经具备一个细节的实现, 我们再去做其他细节的实现. 岂不是很怪异. 这个类中的出现的方法也是匪夷所思. 并且可能用户实现的具体策略也不一定是内存方法的缓存. 在命名上的限制也是很不友好.
总结一句话: 依赖抽象, 而不依赖具体实现
接口隔离原则 ISP
全称: Interface Segregation Principle
定义: 类间的依赖关系应该建立在最小的接口上.
接口隔离的原则就是让系统接口耦合, 从而更容易重构, 更改和重新部署.
对于操作IO或者网络我们总是需要在finally中确保资源的释放. 如下
FileOutputStream fileOutputStream = null; try{ fileOutputStream = new FileOutputStream(cacheDir+imgUrl); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); }catch (IOException e) { e.printStackTrace(); }finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } |
这是多么蛋疼的代码… 一堆堆的花括号.
对于可关闭的对象, 都具备一个接口Closeable. 这个接口一个空接口, 也就是标识接口. 作用? 就是标识这个对象可以调用close()被关闭. 体现了一类对象的某一个特性. 而且这个特性建立在了最小接口的原则上.
那么编写这么一段代码
public class CloseUtils {
// 整个方法通过最小接口的特性, 实现了隔离其他无用的属性. 只关心 // Closeable接口即可. 接口隔离
// 参数 对应了里氏替换的原则 public static void fastClose(Closeable closeObj){ if (null != closeObj){ try { // close()方法的调用, 对应了 依赖倒置原则 closeObj.close(); } catch (IOException e) { e.printStackTrace(); } } } } |
之前的代码调用就变成了
FileOutputStream fileOutputStream = null; try{ fileOutputStream = new FileOutputStream(cacheDir+imgUrl); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); }catch (IOException e) { e.printStackTrace(); }finally { CloseUtils.fastClose(fileOutputStream); } |
不仅简单, 而且方便到处调用, 看起来也舒服多了.
迪米特原则 LOD
Law of Demeter
就是一个对象应该尽可能少的关联其他对象.
通俗讲, 一个类应该对自己需要耦合或调用的类知道的最少, 类的内部如何实现与调用者或者依赖者没有关系, 调用者或者依赖者只需要知道他需要的方法即可, 其他的一概不关心. 因为类与类之间的关系越密切, 耦合度也就越大, 当一个类发生改变时, 对另一个类的影响也会变大.
共同学习,写下你的评论
评论加载中...
作者其他优质文章