Android 系统有更高的灵活性 —— 接口隔离原则

111 阅读5分钟

接口隔离原则

接口隔离原则英文全称是 Interface Segregation Principles,缩写是 ISP。ISP 的定义是:客户端不应该依赖它不需要的接口。另一种定义是:类间的依赖关系应该建立在最小的接口上。接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。

例子

接口隔离原则说白了就是,让客户端依赖的接口尽可能地小,这样说可能还是有点抽象,我们还是以一个示例来说明一下。在此之前我们来说一个场景,在 Java6 及之前的 JDK 版本,有一个非常讨厌的问题,那就是在使用了 OutputStream 或者其他可关闭的对象之后,我们必须保证它们最终被关闭了,我们的 SD 卡缓存类中就有这样的代码。

@Override
public void put(String url, Bitmap bmp) {
    // 将Bitmap写入文件中
    FileOutputStream fos = null;
    try {
        // 构建图片的存储路径 ( 省略了对url取md5)
        fos = new FileOutputStream("sdcard/cache/" + imageUrl2MD5(url));
        bmp.compress(CompressFormat.JPEG, 100, fos);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if ( fos != null ) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

我们看到的这段代码可读性非常差,各种 try...catch 嵌套都是些简单的代码,但是会严重影响代码的可读性,并且多层级的大括号很容易将代码写到错误的层级中。大家应该对这类代码也非常反感,那我们看看如何解决这类问题。

我们可能知道 Java 中有一个 Closeable 接口,该接口标识了一个可关闭的对象,它只有一个 close 方法,如图 1-4 所示。我们要讲的 FileOutputStream 类就实现了这个接口。我们从图 1-4 中可以看到,还有 100 多个类实现了 Closeable 这个接口,这意味着,在关闭这 100 多个类型的对象时,都需要写出像 put 方法中 finally 代码段那样的代码。既然都是实现了 Closeable 接口,那只要建一个方法统一来关闭这些对象不就可以了么?于是写下来如下的工具类。

image.png

/**
 * 关闭工具类
 */
public abstract class CloseUtils {
    /**
     * 关闭Closeable对象
     * 
     * @param closeable
     */
    public static void closeQuietly(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

我们再看看把这段代码运用到上面的 put 方法中的效果如何。

@Override
public void put(String url, Bitmap bmp) {
    // 将Bitmap写入文件中
    FileOutputStream fos = null;
    try {
        // 构建图片的存储路径 ( 省略了对url取md5)
        fos = new FileOutputStream("sdcard/cache/" + imageUrl2MD5(url));
        bmp.compress(CompressFormat.JPEG, 100, fos);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        CloseUtils.closeQuietly(fileOutputStream);
    }
}

代码简洁了很多!而且这个 closeQuietly 方法可以运用到各类可关闭的对象中,保证了代码的重用性。CloseUtils 的 closeQuietly 方法的基本原理就是依赖于 Closeable 抽象而不是具体实现(这不是依赖倒置原则么),并且建立在最小化依赖原则的基础上,它只需要知道这个对象是可关闭,其他的一概不关心,也就是这里的接口隔离原则。

试想一下,如果在只是需要关闭一个对象时,它却暴露出了其他的接口函数,如 OutputStream 的 write 方法,这就使得更多的细节暴露在客户端代码面前,不仅没有很好地隐藏实现,还增加了接口的使用难度。而通过 Closeable 接口将可关闭的对象抽象起来,这样只需要客户端依赖于 Closeable 就可以对客户端隐藏其他的接口信息,客户端代码只需要知道这个对象可关闭(只可调用 close 方法)即可。ImageLoader 中的 ImageCache 就是接口隔离原则的运用,ImageLoader 只需要知道该缓存对象有存、取缓存图片的接口即可,其他的一概不管,这就使得缓存功能的具体实现对 ImageLoader 隐藏。这就是用最小化接口隔离了实现类的细节,也促使我们将庞大的接口拆分到更细粒度的接口当中,这使得我们的系统具有更低的耦合性、更高的灵活性。

# Android 让项目拥有变化的能力 —— 依赖倒置原则

总结

Bob 大叔(RobertCMartin)在 21 世纪早期将单一职责、开闭原则、里氏替换、接口隔离以及依赖倒置(也称为依赖反转) 5 个原则定义为 SOLID 原则,作为面向对象编程的 5 个基本原则。当这些原则被一起应用时,它们使得一个软件系统更清晰、简单,最大程度地拥抱变化。SOLID 被典型地应用在测试驱动开发上,并且是敏捷开发以及自适应软件开发基本原则的重要组成部分。我们发现这几大原则可以化为这几个关键词:抽象、单一职责、最小化。那么,在实际开发过程中如何权衡、实践这些原则,大家需要在实践中多思考与领悟,正所谓“学而不思则罔,思而不学则殆”,只有不断地学习、实践、思考,才能够在积累的过程中有一个质的飞越。