面向对象六大原则-里氏替换原则、依赖倒置原则、接口隔离原则
来源:互联网 发布:没有网络又可以玩游戏 编辑:程序博客网 时间:2024/06/08 16:06
这篇我们来学习里氏替换原则、依赖倒置原则、接口隔离原则,这篇是基于上篇的挤出来来进行讲解,如果没有学习上篇的,建议大家去看下实现原理,上篇地址开闭原则
接下来我们先来学习里氏替换原则:
里氏替换原则
1、里氏替换原则英文全称是 Liskov Substitution Principle,缩写是LSP。LSP的第一种定义是:如果对每一个类型为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所有的对象O1都代换成O2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
上面的这种描述确实不太好理解,我们再来看看另一个直截了当的定义。
里氏替换原则第二种定义:所有引用基类的地方必须能透明地使用其子类的对象。
我们都知道面向对象的三大特点是继承、封装、多态,里氏替换原则就是依赖于继承、多态这两大特性。
里氏替换原则简单来说就是,所有引用基类的地方必须能透明地使用其子类的对象。通俗的说,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或一场,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。总归一句话,抽象。
我们还是来看代码吧,android中window与view的关系:
//窗口类public class Window { public void show(View child){ child.draw(); }}
//建立视图抽象,测量视图的宽高为公用代码,绘制实现交给具体的子类public abstract class View{ public abstract void draw(); public void measure(int width, int height){ //测量视图大小 }}
//按钮类的具体实现public class Button extends View { @Override public void draw() { //绘制按钮 }}
//TextView的具体实现public class TextView extends View { @Override public void draw() { //绘制文本 }}
上述的例子中可以看到,window依赖于view,而view定义了一个视图抽象,measure是各个子类共享的方法,子类通过覆写view的draw方法实现具有各自特色的功能,在这里,这个功能就是绘制自身的内容。任何继承自View类的子类都可以设置给show方法,就是所说的里氏替换。通过里氏替换,就可以自定义各式各样、千变万化的view,然后传递给window,window负责组织view,并将view显示到屏幕上。
里氏替换原则的核心原理是抽象,抽象又依赖于继承这个特性,它具有一下优点:
- 代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性。
- 子类与父类基本相似,但又与父类有所区别;
- 提高代码的 。
继承的缺点:
1. 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法;
2. 可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的属性和方法。
事物总是具有两面性,如何权衡利弊都是需要根据具体情况来做出选择并加以处理。里氏替换原则指导我们构建扩展性更好的软件系统。
开闭原则和里氏替换原则往往是生死相依、不弃不离的,通过里氏替换来达到对扩展开放,对修改关闭的效果。
依赖倒置原则
依赖倒置原则英文全称是Dependence Inversion Principle,缩写是DIP。
依赖倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。
依赖倒置原则有以下几个关键点:
1. 高层模块不应该依赖底层模块,两者都应该依赖其抽象;
2. 抽象不应该依赖细节;
3. 细节应该依赖抽象。
在java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是,可以直接被实例化,也就是可以加上一个关键字new产生一个对象。高层模块就是调用端,底层模块就是具体实现类。
依赖倒置原则在java语言中表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。一句话概括:面向接口编程,或者说是面向抽象编程,这里的抽象指的是接口或者抽象类。
如果类与类直接依赖于细节,那么它们之间就有直接耦合,当具体实现需要变化时,意味着要同时修改依赖着的代码,这就限制了系统的可扩展性。大家可以回顾下ImageLoader相关的实现,如果ImageLoader直接依赖于MemoryCache,这个MemoryCache是一个具体实现,而不是一个抽象类或者接口。这导致了ImageLoader直接依赖了具体细节,当MemoryCache不能满足ImageLoader而需要被其他缓存实现替换时,此时就必须修改ImageLoader的代码,例如:
public class ImageLoader { //图片缓存,直接依赖于细节 MemoryCache memoryCache = new MemoryCache(); //线程池,线程数量为CPU的数量 ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public void setImageCache(MemoryCache cache){ memoryCache = cache; } public void displayImage(final String url, final ImageView imageView){ //判断使用哪种缓存 Bitmap bitmap = memoryCache.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); } memoryCache.put(url,bitmap); } }); }}
随着产品升级,用户发现MemoryCache已经不能满足需求,另外用户自定义还必须继承MemoryCache,在命名上的限制,用户体验也不好,并且在ImageLoader中也违反了开闭原则,灵活性也比较差。所有做了以下修改:
//ImageCache缓存抽象public interface ImageCache { public void put(String url, Bitmap bitmap); public Bitmap get(String url);}
public class ImageLoader { //图片缓存,依赖于抽象,并且有一个默认的实现 ImageCache mImageCache = new MemoryCache(); //线程池,线程数量为CPU的数量 ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //设置缓存,依赖于抽象 public void setImageCache(ImageCache cache){ mImageCache = cache; } 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); } }); } 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 (Exception e){ e.printStackTrace(); } return bitmap; }}
这里我们建立了ImageCache抽象,并且让ImageLoader依赖于抽象而不是具体细节。当需求发生变化时,只需要实现ImageCache类或者继承其他已有的ImageCache子类完成相应的缓存功能,然后将具体的实现注入到ImageLoader即可实现缓存功能的替换,这就保证了缓存系统的高可扩展性,有了拥抱变化的能力,这就是依赖倒置原则。
接口隔离原则
接口隔离原则英文全称是Interface Segregation Principles,缩写是ISP。
ISP的定义是:客户端不应该依赖它不需要的接口。另一种定义是:类间的依赖关系应该建立在最小的接口上。接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。
接下来我们还是用最熟悉的代码来看:
public void put(String url, Bitmap bmp){ FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(cacheDir+url); bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream); }catch (Exception e){ e.printStackTrace(); }finally { if(fileOutputStream!=null){ try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
我们可以看到这段代码的可读性非常差,各种try……catch嵌套都是些简单的代码,但是会严重影响代码的可读性,并且多层级的大括号很容易将代码写到错误的层级中。
我们可能知道Java中有一个Closeable接口,该接口标识了一个可关闭的对象,它只有一个close方法。
//java接口package java.lang;public interface AutoCloseable { void close() throws Exception;}package java.io;import java.io.IOException;public interface Closeable extends AutoCloseable { void close() throws IOException;}
既然它是实现了Closeable 接口,为了代码的可读性,我们还是建立一个方法来同意关闭这些对象。
public class CloseUtils { private CloseUtils(){ } /** * 关闭Closeable对象 */ public static void closeQuietly(Closeable closeable){ if (null!= closeable){ try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } }}
//修改后的方法 public void put(String url, Bitmap bmp){ FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(cacheDir+url); bmp.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream); }catch (Exception e){ e.printStackTrace(); }finally { CloseUtils.closeQuietly(fileOutputStream); } }
大家可以看到代码简洁了很多,CloseUtils.closeQuietly()这个方法可以运用到各类可关闭的对象中,保证了代码的重用性。CloseUtils的closeQuietly方法的基本原理就是依赖于Closeable抽象而不是具体实现,这就符合依赖倒置原则,并且建立在最小化依赖原则的基础上,它只需要知道这个对象是可关闭的,其他一概不关心,也就是接口隔离原则。其实前面提到的ImageLoader中的ImageCache就是接口隔离原则的运用。
这里就先给大家介绍到这里,第一遍看不懂的就多读几次,带着思考去分析下,可以学到不少精髓,欢迎大家提建议。
下一篇将为大家介绍迪米特原则
- 面向对象六大原则-里氏替换原则、依赖倒置原则、接口隔离原则
- 面向对象六大原则----里氏替换原则,依赖倒置原则
- 面向对象设计原则--里氏替换原则(LSP)和依赖倒置原则(DIP)
- 面向对象六大原则----接口隔离原则,迪米特原则
- 面向对象程序设计的六大原则(3)- 里氏替换原则
- 面向对象六大原则(三):里氏替换原则
- 面向对象的六大原则(三)-- 里氏替换原则
- 面向对象程序设计六大原则-里氏替换原则
- 面向对象六大原则之里氏替换原则
- 设计模式原则----里氏替换原则,依赖倒置原则
- 面向对象程序设计的六大原则(5)-依赖倒置原则
- 面向对象六大原则(三):依赖倒置原则
- 面向对象六大原则(四):依赖倒置原则
- 面向对象程序设计六大原则-依赖倒置原则
- 面向对象六大原则之依赖倒置原则
- 面向对象原则之里氏替换原则
- 面向对象程序设计的六大原则(4)- 接口隔离原则
- 面向对象六大原则(四):接口隔离原则
- MediBang Paint Pro超级精简版/超精简/懂你版
- 关于字符集和字符编码
- UITableViewController使用
- line-height和height的区别
- android 录音获取相关值
- 面向对象六大原则-里氏替换原则、依赖倒置原则、接口隔离原则
- React开发神器Webpack
- "Android-事件处理机制"之面试必问技能点汇总
- EasyUI基本配置,HelloWorld
- 推荐算法之 slope one 算法
- Android项目新手功能引导页面代码实现
- 基于Unity引擎的简单对象池
- RecyclerView侧滑菜单和listview实现的通讯录侧滑
- 内部类的序列化问题;静态变量不能被序列化的问题