您的位置:首页 > 其它

面向对象六大原则-里氏替换原则、依赖倒置原则、接口隔离原则

2016-07-15 12:02 459 查看
这篇我们来学习里氏替换原则、依赖倒置原则、接口隔离原则,这篇是基于上篇的挤出来来进行讲解,如果没有学习上篇的,建议大家去看下实现原理,上篇地址开闭原则

接下来我们先来学习里氏替换原则:

里氏替换原则

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就是接口隔离原则的运用。

这里就先给大家介绍到这里,第一遍看不懂的就多读几次,带着思考去分析下,可以学到不少精髓,欢迎大家提建议。

下一篇将为大家介绍迪米特原则
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  面向对象 对象 class