solid是什么意思(深入设计原则-SOLID)

介绍

SOLID是什么,它是如何帮助我们写更好的代码的?

SOLID原则由以下5个概念组成:

  1. Single Responsibility(单一职责)
  2. Open/Closed(开闭)
  3. Liskov Substitution(里氏替换)
  4. Interface Segregation(接口隔离)
  5. Dependency Inversion(依赖反转)


简单来说,这些原则帮助我们创造更易维护,易于理解和灵活的软件。因此,随着我们应用的扩大,我们可以降低其复杂性,并在以后为我们省去很多麻烦。


深入说明单一职责原则

像我们从字面意思看到的一样,该原则指出,一个类仅应承担一种责任。此外,它应该只有一个改变的理由(不能同时有很多理由来修改它)。

那么,这个原则是怎么帮助我们写更好的软件的?让我们先看一下它的优点:

  1. 易测试 - 因为职责单一,所以拥有更少的测试用例
  2. 低耦合 - 单个类中较少的功能将具有较少的依赖关系
  3. 易于组织 - 更容易编写代码逻辑


下面我们以代码示例说明:

我们用Book类代表一本书,其属性包括:书名(name),作者(author)和内容(text)

/** * 实体:书 */ public class Book { private String name; private String author; private String text; /** * 文本替换 * @param word * @return */ public String replaceWordInText(String word){ return text.replaceAll(word, text); } /** * 是否包含指定的文本 * @param word * @return */ public boolean isWordInText(String word){ return text.contains(word); } }

现在我们的程序运行的很好,我们能存储任意的书的内容。但是我们无法将书的内容打印出来,无法阅读该怎么办?那么,我们就在新增一个打印内容的方法,如:

void printTextToConsole(){ // 输出文本 }

好的,现在已经可以实现打印内容了。但是,我们这样就违背了“单一职责”(书本身不与打印有什么关系)。为了解决我们的问题,需要实现一个单独的类,该类仅与打印书籍有关:

soli

/** * 书籍打印 */ public class BookPrinter { /** * 打印到控制台 * @param text */ void printTextToConsole(String text){ // printing the text } /** * 打印到其他媒介 * @param text */ void printTextToAnotherMedium(String text){ // do something } }

上面的类不仅与书籍本身解耦,而且能够实现通过各种媒介进行打印内容,本身又是一个职责单一的案例(打印)。


开闭原则

简单来说,一个类应对扩展是打开的,对修改是关闭的(open for extension, closed for modification)。这样一来,我们就可以避免修改现有代码,从而引入新的潜在的问题。当然有个特例,就是如果现有代码中存在已有bug,我们还是应该去解决它的。


比如现在有一个吉他,可以实现基本功能的弹奏:

/** * 吉他 */ public class Guitar { private String make; private String model; private int volume; }

但是用了一段时间后,觉得有点无聊想增加一些音节,让它用起来更加的摇滚。

我们如果直接在原有的Guitar类上修改,可能会把原有的功能破坏掉从而引入新的问题,所以根据“开闭原则”,我们应该在原有基础上进行扩展而不是修改:

/** * 更酷炫的音节 */ public class SuperCoolGuitarWithFlames extends Guitar { private String flameColor; }

通过扩展实现,我们可以保证现有的功能不会受到破坏。

里氏替换原则

这个原则字面意思较难理解,简单来说就是,如果一个类A是类B的子类,那么我们在不中断程序行为的情况下可以把B替换成A,而不影响程序原有的功能。

我们用代码示例说明:

/** * 车 */ public interface Car { /** * 打开引擎 */ void turnOnEngine(); /** * 加速 */ void accelerate(); }

我们定义了一个接口,里面有两个方法,可以实现引擎打开和车加速功能。

下面来看下具体的实现类:

/** * 摩托车 */ public class MotorCar { private Engine engine; public void turnOnEngine() { //turn on the engine! engine.on(); } public void accelerate() { //move forward! engine.powerOn(1000); } }

可以看出摩托车属于车的一种,实现了打开引擎和加速能力,我们继续看其他实现:

/** * 电车 */ public class ElectricCar { public void turnOnEngine() { throw new AssertionError("I don't have an engine!"); } public void accelerate() { //this acceleration is crazy! } }

可以看出,上面的电车虽然实现了Car,但是它没有引擎,所以不具有打开引擎的功能,那么这个就改变了程序的行为,违背了我们说的“里氏替换原则”。

接口隔离原则

简单来说,就是将大的接口切分为更小的接口,这样我们就可以确保实现类只需要关心它们感兴趣的方法。

举个例子,假如我们在动物园工作,具体是熊的“看护人”,那么可以这样定义:

public interface BearKeeper { /** * 给熊洗澡 */ void washTheBear(); /** * 给熊喂食 */ void feedTheBear(); /** * 抚摸熊 */ void petTheBear(); }

我们可以开心喂养熊,但是抚摸熊可能有危险,但是我们的接口定义的相当大,我们别无选择。那么,现在我们将接口拆分一下:

public interface BearCleaner { void washTheBear(); } public interface BearFeeder { void feedTheBear(); } public interface BearPetter { void petTheBear(); }

通过拆分,我们就可以自由实现我们感兴趣的方法,

public class BearCarer implements BearCleaner, BearFeeder { public void washTheBear() { } public void feedTheBear() { } }

我们可以将抚摸熊的“福利”给那么胆大或疯狂的人:

public class CrazyPerson implements BearPetter { public void petTheBear() { //Good luck with that! } }依赖反转

这个原则主要是为了解耦。代替低级模块依赖高级模块,二者都应该依赖抽象。

举个例子,假如我们现有一台Windows98电脑:

public class Windows98Machine {}

但是没有显示器和键盘对我们来说有什么用呢?于是我们给这台电脑新增显示器和键盘:

public class Windows98Machine { private final StandardKeyboard keyboard; private final Monitor monitor; public Windows98Machine() { monitor = new Monitor(); keyboard = new StandardKeyboard(); } }

上面代码运行的很好,我们的电脑已经实现了显示器和键盘,那么问题解决了吗?没有,我们又把这三个类紧密结合在一起了。这不仅使Windows98Machine难以测试,而且还失去了将StandardKeyboard替换为其他类的能力。

让我们把Keyboard抽象出来:

public interface Keyboard { } public class Windows98Machine{ private final Keyboard keyboard; private final Monitor monitor; public Windows98Machine(Keyboard keyboard, Monitor monitor) { this.keyboard = keyboard; this.monitor = monitor; } }

public class StandardKeyboard implements Keyboard { }

现在Windows98Machine就与StandardKeyboard解耦了,通过依赖反转(依赖抽象而不是具体类)完成了解耦。


总结

在本文中我们深入研究了面向对象的SOLID原则,并通过代码示例说明其原理和实现。

您可以还会对下面的文章感兴趣:

使用微信扫描二维码后

点击右上角发送给好友