面向对象六大原则----开闭原则
2016-07-27 17:59
465 查看
Java 中面向对象编程六大原则:
单一职责原则 英文名称是Single Responsibility Principle,简称SRP开闭原则 英文全称是Open Close Principle,简称OCP
里氏替换原则 英文全称是Liskov Substitution Principle,简称LSP
依赖倒置原则 英文全称是Dependence Inversion Principle,简称DIP
接口隔离原则 英文全称是InterfaceSegregation Principles,简称ISP
迪米特原则 英文全称为Law of Demeter,简称LOD,也称为最少知识原则(Least Knowledge Principle)
让程序更稳定、更灵活——开闭原则
开闭原则的英文全称是Open Close Principle,简称OCP,它是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。软件开发过程中,最不会变化的就是变化本身。产品需要不断地升级、维护,没有一个产品从第一版本开发完就再没有变化了,如果因为变化、升级和维护等原因需要对软件原有代码进行修改时,这就可能会将错误引入原本已经经过测试的旧代码中,破坏原有系统。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。那么如何确保原有软件模块的正确性,以及尽量少地影响原有模块,答案就是尽量遵守本章要讲述的开闭原则
继续上一章对ImageLoader进行分析,我们在第一轮重构之后的ImageLoader使用了单一原则,让代码职责单一、结构清晰,但是我们在使用中发现我们只实现了内存缓存图片,通过内存缓存解决了每次从网络加载图片的问题,但是,Android应用的内存很有限,且具有易失性,即当app重新启动之后,原来已经加载过的图片将会丢失,这样重启之后就又需要重新下载。这又会导致加载缓慢、耗费用户流量的问题。所以我们应该引入SD卡缓存,这样下载过的图片就会缓存到本地,即使重启应用也不需要重新下载了。
根据上一章的单一原则,我们写一个DiskCache.java类,将图片缓存到SD卡中:
public class DiskCache { private String mCacheDirName = "imageCache"; private String mCacheDirPath; private File mCachDir = null; public DiskCache(Context context){ initDiskCacheDir(context); } //初始化SD卡缓存的目录位置 public void initDiskCacheDir(Context context) { String cachePath; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { //获取外部存储卡的位置,/sdcard/Android/data/xxxx(应用包名)/cache cachePath = context.getExternalCacheDir().getPath(); } else { //如果无法获取外部存储卡的位置,则使用应用自己的私用空间,/data/data/xxxx(应用包名)/cache cachePath = context.getCacheDir().getPath(); } mCacheDirPath = cachePath+File.separator+mCacheDirName; mCachDir = new File(mCacheDirPath); if(!mCachDir.exists()){ mCachDir.mkdirs(); } } //将下载的图片流缓存到sd卡里 public void put(String key, InputStream inputStream){ if(!mCachDir.exists()){ return; } //对传入的key值进行MD5处理 String savedFilePath = mCacheDirPath+File.separator+hashKeyForDisk(key); try { FileOutputStream f = new FileOutputStream(new File(savedFilePath)); BufferedInputStream in = new BufferedInputStream(inputStream); BufferedOutputStream out = new BufferedOutputStream(f); int n; byte[] buf = new byte[4096]; while ((n = in.read(buf)) != -1) { out.write(buf,0,n); } in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } //根据传入的key值返回缓存的文件流 public InputStream get(String key){ if(!mCachDir.exists()){ return null; } String savedFilePath = mCacheDirPath+File.separator+hashKeyForDisk(key); File bitmapFile = new File(savedFilePath); if(bitmapFile.exists()){ try { InputStream in = new FileInputStream(bitmapFile); return in; } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } return null; } /** * 将Key用MD5码编码 * @param key * @return 返回MD5码后的编码 */ public String hashKeyForDisk(String key) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(key.hashCode()); } return cacheKey; } private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } }
然后我们修改ImageLoader类加入我们的DiskCache:
public void setDiskCahe(Context context){ mDiskCache = new DiskCache(context); }再修改下载和显示逻辑,加入判断SD卡缓存里有没有缓存过要显示的图片,有缓存则直接取缓存显示,避免再下载
public void displayImage(final String url, final ImageView imageView) { imageView.setTag(url); //先从cache中取图片 if(mMemImageCache.get(url)!=null){ imageView.setImageBitmap(mMemImageCache.get(url)); return; } if(mDiskCache != null){ InputStream in = mDiskCache.get(url); if(in != null){ Bitmap bitmap = BitmapFactory.decodeStream(in); imageView.setImageBitmap(bitmap); return; } } mExecutorService.submit(new Runnable() { @Override public void run() { Bitmap bitmap = downloadImage(url); if (bitmap == null) { return; } if (imageView.getTag().equals(url)) { Message msg = mHandler.obtainMessage(); imageView.setTag(bitmap); msg.obj = imageView; mHandler.sendMessage(msg); } mMemImageCache.put(url, bitmap); } }); } public Bitmap downloadImage(String imageUrl) { Bitmap bitmap = null; try { URL url = new URL(imageUrl); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setDoInput(true); //允许输入流,即允许下载 conn.setUseCaches(false); //不使用缓冲 conn.setRequestMethod("GET"); //使用get请求 InputStream is = conn.getInputStream(); //获取输入流,此时才真正建立链接 if(mDiskCache!=null){ //缓存到硬盘上 mDiskCache.put(imageUrl,is); } bitmap = BitmapFactory.decodeStream(mDiskCache.get(imageUrl)); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } return bitmap; }
通过上面的修改,现在我们只需在使用ImageLoader时,调用一下setDiskCahe(Context context)方法就能使用SD卡缓存图片,这是不是很方便,但是我们在添加一个DiskCache缓存时修改了许多ImageLoader代码,如果我们需要更多缓存方式或更多缓存策略,比如只使用内存缓存,或内存缓存和SD卡缓存同时使用等等需求,每次加新的缓存方法时都要修改原来的代码,这样很可能会引入Bug,而且会使原来的代码逻辑变得越来越复杂,按照上面这样的方法实现,用户也不能自定义缓存实现。
软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的,这就是开放-关闭原则。也就是说,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。如何实现ImageLoader类需要的cache能扩展呢?我们可以定义一个ImageCache接口:
public interface ImageCache { void put(String key, Bitmap bitmap); Bitmap get(String key); }然后所有的具体Cache类实现这个接口,而我们的ImageLoader类只需操作这个ImageCache类就可以了.这就实现了用抽象类或接口来实现原功能的扩展。下面看一下UML图:
如上图,所有具体的Cache类实现了ImageCahe这个接口,而ImageLoader类只依赖于ImageCache类,这样就达到了可以不修改原有代码,而可以无限通过实现ImageCache这个接口来扩展ImageLoader类的缓存图片功能了。
于是ImageLoader类里增加设置ImageCache:
public void setImageCache(ImageCache cache){ mImageCache = cache; }
通过setImageCache(ImageCache cache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高。MemoryCache、DiskCache、DoubleCache缓存图片的具体实现完全不一样,但是,它们的一个特点是都实现了ImageCache接口。当用户需要自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,然后构造该类的对象,并且通setImageCache(ImageCache cache)注入到ImageLoader中,这样ImageLoader就实现了变化万千的缓存策略,而扩展这些缓存策略并不会导致ImageLoader类的修改。这就是开闭原则!
开闭原则指导我们,当软件需要变化时,应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。这里的“应该尽量”4个字说明OCP原则并不是说绝对不可以修改原始类的,当我们嗅到原来的代码“腐化气味”时,应该尽早地重构,以使得代码恢复到正常的“进化”轨道,而不是通过继承等方式添加新的实现,这会导致类型的膨胀以及历史遗留代码的冗余。当然我们在开发过程中也没有那么理想化的状况,完全地不用修改原来的代码,因此,在开发过程中需要自己结合具体情况进行考量,是通过修改旧代码还是通过继承使得软件系统更稳定、更灵活,在保证去除“代码腐化”的同时,也保证原有模块的正确性。
代码github地址:点击打开链接
更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号: Android老鸟
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试