likes
comments
collection
share

解锁面向对象设计的六大原则:让你的代码更优雅、更可维护的秘诀

作者站长头像
站长
· 阅读数 3

本文微信公众号「安卓小煜」首发

1. 背景

大家应该都听过面向对象设计的 SOLID 原则,本文我们就来唠一唠面向对象设计的六大原则,也就是 SOLID+迪米特原则。

2. SOLID 原则

2.1 单一职责原则

单一职责原则的英文名称是 Single Responsibility Principle,缩写是 SRP

SRP 的定义是,就一个类而言,应该仅有一个引起它变化的原因

简单来说,一个类中应该是一组相关性很高的函数、数据的封装

解锁面向对象设计的六大原则:让你的代码更优雅、更可维护的秘诀

我们要设计一个图片加载器。这个时候一个类里面如果我们这样写:

class ImageLoader {
    void display(ImageView img) {
        // TODO
    }
    void setImageCache(String uri, Bitmap bitmap) {
        // TODO
    }
    Bitmap getImageCache(String uri) {
        // TODO
    }
}

我们会发现这个图片加载器类既承担了图片加载的功能,也承担了图片缓存的功能。 当我们图片加载的逻辑修改时,我们需要来改动这个类的代码。 当我们图片缓存的逻辑修改时,也需要来改动这个类的代码。 也就是说引起我们图片加载器类变化的原因存在多个,不符合 SRP,因此我们需要对其进行功能的解耦,把缓存给单独拎出来放到另一个类里面。

2.2 开闭原则

开闭原则的英文全称是 Open Close Principle,缩写是 OCP

OCP 的定义是,软件中的对象应该对于扩展是开放的,但是,对于修改是封闭的

我们设计的图片加载器。 图片缓存应该支持多种缓存方式,并且要支持用户自定义缓存。 因此需要提供一个接口给用户设置,通过依赖注入的方式来确定具体的缓存策略。

interface ImageCache {
    void setImageCache(String uri, Bitmap bitmap);
    Bitmap getImageCache(String uri);
}

class ImageLoader {
    // 提供了接口让用户可以支持缓存的自定义
    void setCacheStrategy(ImageCache cache) {
        // TODO
    }
}

2.3 里氏替换原则

里氏替换原则的英文全称是 Liskov Substitution Principle,缩写是 LSP不是lǎo sè pī

LSP 直截了当的定义是,所有引用基类的地方必须能透明地使用其子类的对象

还是以我们设计的图片加载器为例来进行说明。 我们可以看到,缓存的多种实现方式,依靠的就是里氏替换原则,有了这个原则,用户如果要自定义缓存的实现方式,只需要写一个子类继承我们的基类 ImageCache ,然后通过依赖注入设置进去即可。 而这里依赖注入可以成功的前提正是由于引用基类的地方能使用其子类的对象。所以说

里氏替换原则跟开闭原则是不离不弃的,通过里氏替换原则来达到对扩展开放,对修改封闭的效果。

2.4 接口隔离原则

接口隔离原则的英文全称是 Interface Segregation Principle,缩写是 ISP

ISP 的定义是,客户端不应该依赖它不需要的接口

我们知道,在使用流进行文件的读写之后,我们要及时进行关闭,否则就可能导致内存泄漏。

我们使用流来读操作一般会写出下面的代码:

String filePath = "path/to/your/file.txt"; // 请替换为你的文件路径  
BufferedReader reader = null;  

try {  
    reader = new BufferedReader(new FileReader(filePath));  
    String line;  
    while ((line = reader.readLine()) != null) {  
        System.out.println(line);  
    }  
} catch (IOException e) {  
    e.printStackTrace();  
} finally {  
    if (reader != null) {  
        try {  
            reader.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

我们会发现,流的关闭写的有点多层嵌套的样子,不够简洁

if (reader != null) {  
    try {  
        reader.close();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}  

因此我们会考虑把流的关闭代码抽取出来,写到一个工具类里面,类似:

public void close(BufferedReader reader) {
    if (reader != null) {  
        try {  
            reader.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    } 
}

当然,有了上文的学习基础之后,大家应该发现这个工具类写的有点问题,它依赖的是具体而不是抽象,如果我们换一个读文件的 API,我们这个方法就不能使用了。 我们跟踪了 BufferedReader,发现它的父类是 Reader,那是不是代表着我们使用 Reader 来做参数就 OK 了呢?

解锁面向对象设计的六大原则:让你的代码更优雅、更可维护的秘诀

如果你参数用 Reader,那么对于 Writer 或者 InputStream 又不通用了。 这里的一个做法其实就体现了 ISP

对于有关闭功能的类,它都可以实现 Closeable 接口,这个接口里面就一个 close 方法

因此,如果你自己写的一个类,有关闭的功能,也可以实现 Closeable 接口

Closeable 接口只提供了一个关闭方法,因此如果客户端有关闭需要,就可以依赖它,而没有关闭需求的类,则不需要依赖这个接口

所以对于关闭功能的封装,我们可以写出如下代码:

public void close(Closeable closeable) {
    if (closeable != null) {  
        try {  
            closeable.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    } 
}

ISP 使得我们的这个公共方法可以给那些有关闭需求的类使用

2.5 依赖倒置原则

依赖倒置原则英文全称是 Dependence Inversion Principle,缩写是 DIP

DIPJava 语言中的表现是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。

上文中的图片加载器,缓存的多种实现方式,是通过依赖注入来实现的。其依赖关系是通过接口 ImageCache 来产生的。


以上就是面向对象编程的 SOLID 原则

单一职责 SRP 开闭原则 OCP 里氏置换 LSP 接口隔离 ISP 依赖倒置 DIP(依赖反转)

3. 迪米特原则

迪米特原则的英文全称是 Law Of Demeter,缩写是 LOD

LOD 的定义是,一个对象应该对其他对象有最少的了解

大家应该都找过房子。 找房子我们可以抽象出三个角色:租户、房子、中介。 对于租户来说,他只需要把想要的房子面积和接受价格等要求给到中介即可,而不需要自己去判断这个房子是不是符合他的要求,这些都是属于中介的职责。因此对于租户来说,他不需要对房子有了解。

4. 总结

以上就是面向对象设计的六大原则,也就是 SOLID+迪米特原则。 大家在进行软件开发时,心里要牢记这些原则,但是又不硬套原则。

不管是原则还是工具,最终的目的都只是为了让我们的软件开发更有效率。


参考: Android 源码设计模式解析与实战