六大设计原则之开闭原则
2017-08-12 14:37
92 查看
概念
开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是
封闭的。简单说就是,对于经过测试已经稳定的代码禁止修改,如果有新的需求只允许扩展原有代
码。因为修改可能将错误引入到已经经过测试稳定的代码,破坏原有系统。当然这只是理想中的状
态,在实际开发中,修改原有代码和扩展代码都同时存在的,我们要做的就是不断的去接近这种理
想的状态。开闭原则是面向对象编程世界里最基础的设计原则,它指导我们如何构建一个稳定的、
灵活的系统。
那么我们怎么扩展代码,怎么封闭代码?还是用上一篇文章中的例子来说明。
在单一职责里面的例子(六大设计原则之单一职责),似乎是完美的,通过内存缓存解决了每次都
从网络加载图片的问题,但是当内存被回收或者应用重启,原来加载过的图片就消失,需要重新加
载,这样耗流量耗时间,体验很差。于是,现在提出了新的缓存策略:用SD卡缓存。文章有点长,
代码有点多,是为了引出问题,然后解决问题,总之专研技术需要耐心,最好自己敲一遍代码,体
会其中的不同,通过层层优化,最后达到较理想的效果。
优化之前的代码逻辑
sd卡缓存:
因为需要将图片缓存到SD卡中,所以需要更改ImageLoader的代码,具体代码如下
通过以上代码,仅增加了DiskCache类和往imageLoader类中加入少量的代码就增加了SD卡缓存的
功能,且我们可以直接设置useDiskCache的值来决定是否使用SD卡缓存,很是方便。但是聪明的
你一定发现了上面的例子中,当用户使用了内存就不能用SD卡缓存,反之亦然。能不能再优化下,
两种缓存策略都用,即优先使用内存缓存,如果没有图片再使用SD卡缓存,如果SD卡中也没有图片,
最后才从网络中获取。于是,我们新建一个双缓存类:
双缓存类
同样,ImageLoader类也需要更新,代码如下
有没有发现,每当我们新增加一个需求,我们所有的类都需要改动,麻烦不说,万一有其他类正在
调用原有的代码,是不是可能会惹来“麻烦”?而且使用了很多的判断逻辑导致代码很复杂,这是
很容易出现bug的,而且是不容易找出.再者,用户不能自己实现/定义缓存注入到ImageLoader中,可
扩展性差,背离了面向对象的思想。
“软件中的对象(类。模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开
放关闭原则。”也就是说当需求变化时,我们应该尽量通过扩展方式来实现变化,而不是通过修改
已有的代码来实现。为了实现这样的效果,我们需要对前面的代码来一次重构,对上面的代码重构
中涉及的几个类的代码都发生了不同程度的改变,所以我打算把所有的各类的代码都贴出来。
优化之后的代码逻辑
我们先看下UML图,对UML图不了解的这篇文章深入浅出UML类图对你有帮助。
代码中的注释已经很详细了,就不在详细解释。
1.ImageLoader类
经过这次重构,没有了那么多的 if-else 语句了,没有了各种各样的缓存实现对象、布尔变量,代码
也清晰简单了很多。不过这里用到的ImageCache 已经不是之前的那个ImageCache类了,而是把
他提取成图片缓存接口,抽象缓存的功能。我们来看看该接口中的声明:
2.图片缓存接口
该接口定义了获取和缓存图片的两个函数,缓存的key是图片的url,值是图片本身。然后让内存缓
存、SD卡缓存或者
双缓存都实现该接口。
3.内存缓存
4.SD卡缓存
5.双缓存
6.最后是调用
细心的读者可能注意到了,在ImageLoader中增加了SetImageCache(ImageCache cache)函
数,用户可以通过该函数设置缓存实现,不再需要去更改Imageloader函数中的代码了,也就
是常说的依赖注入。这样也就达到了我们的最初目的。
我们来看下用户如何设置缓存的
最后来下总结:
1.需求的变更
内存缓存实现图片缓存–>磁盘缓存实现图片缓存–>内存和磁盘的双缓存实现图片缓存。
2.如何优化
从开始的每次需求的变更都需要去更改原先的代码,而且代码中有了不少的逻辑判断,容易
出现失误。–>我们定义了一个规则(ImageCache接口),当我们的需求变化时,只需要更改
实现这个接口,并把它注入缓存类(ImageLoader类),就能在不改变原有代码的条件下实
现了我们新的需求,也没有了那么多的逻辑判断。
开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是
封闭的。简单说就是,对于经过测试已经稳定的代码禁止修改,如果有新的需求只允许扩展原有代
码。因为修改可能将错误引入到已经经过测试稳定的代码,破坏原有系统。当然这只是理想中的状
态,在实际开发中,修改原有代码和扩展代码都同时存在的,我们要做的就是不断的去接近这种理
想的状态。开闭原则是面向对象编程世界里最基础的设计原则,它指导我们如何构建一个稳定的、
灵活的系统。
那么我们怎么扩展代码,怎么封闭代码?还是用上一篇文章中的例子来说明。
在单一职责里面的例子(六大设计原则之单一职责),似乎是完美的,通过内存缓存解决了每次都
从网络加载图片的问题,但是当内存被回收或者应用重启,原来加载过的图片就消失,需要重新加
载,这样耗流量耗时间,体验很差。于是,现在提出了新的缓存策略:用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类),就能在不改变原有代码的条件下实
现了我们新的需求,也没有了那么多的逻辑判断。
相关文章推荐
- 设计模式六大原则之单一职责原则
- 六大设计原则
- 设计模式六大原则
- ### 设计模式:六大原则
- 设计模式之禅【六大设计原则】
- 设计模式六大原则:单一职责原则
- 设计模式六大原则(5):迪米特法则
- 设计模式六大原则(6):开闭原则
- 设计模式六大原则(5):迪米特法则
- 设计模式六大原则(1):单一职责原则
- 设计模式之六大原则
- Java设计模式六大原则
- 设计模式六大原则(2):里氏替换原则
- IOS设计模式的六大设计原则之里氏替换原则(LSP,Liskov Substitution Principle)
- 设计模式六大原则
- java设计模式六大原则(2):里氏替换原则
- 设计模式六大原则
- 设计模式六大原则(PHP)
- 设计模式——六大设计原则
- 知识学习——设计模式的六大设计原则