乐趣区

关于设计模式:超易懂原来SOLID原则要这么理解

说到 SOLID 准则,置信有过几年工作教训的敌人都有个大略印象,但就是不晓得它具体是什么。甚至有些工作了十几年的敌人,它们对 SOLID 准则的了解也停留在外表。明天咱们就来聊聊 SOLID 准则以及它们之间的关系。

什么是 SOLID 准则

SOLID 准则其实是用来领导软件设计的,它一共分为五条设计准则,别离是:

  • 繁多职责准则(SRP)
  • 开闭准则(OCP)
  • 里氏替换准则(LSP)
  • 接口隔离准则(ISP)
  • 依赖倒置准则(DIP)

繁多职责准则(SRP)

繁多职责准则(Single Responsibility Principle),它的定义是:应该有且仅有一个起因引起类的变更。 简略地说:接口职责应该繁多,不要承当过多的职责。 用生存中肯德基的例子来举例:负责前台收银的服务员,就不要去餐厅开盘子。负责餐厅开盘子的就不要去做汉堡。

繁多职责实用于接口、类,同时也实用于办法。例如咱们须要批改用户明码,有两种形式能够实现,一种是用「批改用户信息接口」实现批改明码,一种是新起一个接口来实现批改明码性能。在繁多职责准则的领导下,一个办法只承当一个职能,所以咱们应该新起一个接口来实现批改明码的性能。

繁多职责准则的重点在于职责的划分,很多时候并不是变化无穷的,须要依据理论状况而定。繁多职责可能使得类复杂性升高、类之间职责清晰、代码可读性进步、更加容易保护。但它的毛病也很显著,就是对技术人员要求高,有些时候职责难以辨别。

咱们在设计一个类的时候,能够先从粗粒度的类开始设计,等到业务倒退到肯定规模,咱们发现这个粗粒度的类办法和属性太多,且常常批改的时候,咱们就能够对这个类进行重构了,将这个类拆分成粒度更细的类,这就是所谓的继续重构。

开闭准则(OCP)

开闭准则(Open Closed Principle),它的定义是:一个软件实体,如类、模块和函数应该对扩大凋谢,对批改敞开。 简略地说:就是当他人要批改软件性能的时候,使得他不能批改咱们原有代码,只能新增代码实现软件性能批改的目标。

这听着有点玄乎,我来举个例子吧。

这段代码模仿的是对于水果剥皮的处理程序。如果是苹果,那么是一种拨皮办法;如果是香蕉,则是另一种剥皮办法。如果当前还须要解决其余水果,那么就会在前面加上很多 if else 语句,最终会让整个办法变得又臭又长。如果恰好这个水果中的不同种类有不同的剥皮办法,那么这外面又会有很多层嵌套。

if(type == apple){//deal with apple} else if (type == banana){//deal with banana} else if (type == ......){//......}

能够看得出来,下面这样的代码并没有满足「对拓展凋谢,对批改关闭」的准则。每次须要新增一种水果,都能够间接在原来的代码上进行批改。长此以往,整个代码块就会变得又臭又长。

如果咱们对剥水果皮这件事件做一个形象,剥苹果皮是一个具体的实现,剥香蕉皮是一个具体的实现,那么写出的代码会是这样的:

public interface PeelOff {void peelOff();
}

public class ApplePeelOff implement PeelOff{void peelOff(){//deal with apple}
}

public class BananaPeelOff implement PeelOff{void peelOff(){//deal with banan}
}

public class PeelOffFactory{private Map<String, PeelOff> map = new HashMap();

    private init(){//init all the Class that implements PeelOff interface}
}

.....

public static void main(){
    String type = "apple";
    PeelOff peelOff = PeelOffFactory.getPeelOff(type);  //get ApplePeelOff Class Instance.
    peelOff.pealOff();}

下面这种实现形式使得他人无奈批改咱们的代码,为什么?

因为当须要对西瓜剥皮的时候,他会发现他只能新增一个类实现 PeelOff 接口,而无奈再原来的代码上批改。这样就实现了「对拓展凋谢,对批改关闭」的准则。

里氏替换准则(LSP)

里氏替换准则(LSP)的定义是:所有援用基类的中央必须能通明地应用其子类的对象。 简略地说:所有父类能呈现的中央,子类就能够呈现,并且替换了也不会呈现任何谬误。 例如上面 Parent 类呈现的中央,能够替换成 Son 类,其中 Son 是 Parent 的子类。

Parent obj = new Son();
等价于
Son son  = new Son();

这样的例子在 Java 语言中是十分常见的,但其外围要点是:替换了也不会呈现任何的谬误。 这就要求子类的所有雷同办法,都必须遵循父类的约定,否则当父类替换为子类时就会出错。 这样说可能还是有点形象,我举个例子。

public class Parent{
    // 定义只能扔出空指针异样
    public void hello throw NullPointerException(){}
}
public class Son extends Parent{public void hello throw NullPointerException(){
        // 子类实现时却扔出所有异样
        throw Exception;
    }
}

下面的代码中,父类对于 hello 办法的定义是只能扔出空指针异样,但子类笼罩父类的办法时,却扔出了其余异样,违反了父类的约定。那么当父类呈现的中央,换成了子类,那么必然会出错。

其实这个例子举得不是很好,因为这个在编译层面可能就有谬误。但表白的意思应该是到位了。

而这里的父类的约定,不仅仅指的是语法层面上的约定,还包含实现上的约定。有时候父类会在类正文、办法正文里做了相干约定的阐明,当你要覆写父类的办法时,须要弄懂这些约定,否则可能会呈现问题。例如子类违反父类申明要实现的性能。比方父类某个排序办法是从小到大来排序,你子类的办法居然写成了从大到小来排序。

里氏替换准则 LSP 重点强调:对使用者来说,可能应用父类的中央,肯定能够应用其子类,并且预期后果是统一的。

接口隔离准则(ISP)

接口隔离准则(Interface Segregation Principle)的定义是:类间的依赖关系应该建设在最小的接口上。 简略地说:接口的内容肯定要尽可能地小,能有多小就多小。

举个例子来说,咱们常常会给他人提供服务,而服务调用方可能有很多个。很多时候咱们会提供一个对立的接口给不同的调用方,但有些时候调用方 A 只应用 1、2、3 这三个办法,其余办法基本不必。调用方 B 只应用 4、5 两个办法,其余都不必。接口隔离准则的意思是,你应该把 1、2、3 抽离进去作为一个接口,4、5 抽离进去作为一个接口,这样接口之间就隔离开来了。

那么为什么要这么做呢? 我想这是为了隔离变动吧! 想想看,如果咱们把 1、2、3、4、5 放在一起,那么当咱们批改了 A 调用刚才用到 的 1 办法,此时尽管 B 调用方基本没用到 1 办法,然而调用方 B 也会有产生问题的危险。而如果咱们把 1、2、3 和 4、5 隔离成两个接口了,我批改 1 办法,相对不会影响到 4、5 办法。

除了改变导致的变动危险之外,其实还会有其余问题,例如:调用方 A 埋怨,为什么我只用 1、2、3 办法,你还要写上 4、5 办法,减少我的了解老本。调用方 B 同样会有这样的困惑。

在软件设计中,ISP 提倡不要将一个大而全的接口扔给使用者,而是将每个使用者关注的接口进行隔离。

依赖倒置准则(DIP)

依赖倒置准则(Dependence Inversion Principle)的定义是:高层模块不应该依赖底层模块,两者都应该依赖其形象。形象不应该依赖细节,即接口或抽象类不依赖于实现类。细节应该依赖形象,即实现类不应该依赖于接口或抽象类。 简略地说,就是说咱们应该面向接口编程。通过形象成接口,使各个类的实现彼此独立,实现类之间的松耦合。

如果咱们每个人都能通过接口编程,那么咱们只须要约定好接口定义,咱们就能够很好地单干了。 软件设计的 DIP 提倡使用者依赖一个形象的服务接口,而不是去依赖一个具体的服务执行者,从依赖具体实现转向到依赖形象接口,倒置过去。

SOLID 准则的实质

咱们总算把 SOLID 准则中的五个准则说完了。 但说了这么一通,如同是懂了,然而如同什么都没记住。 那么咱们就来盘一盘他们之间的关系。ThoughtWorks 上有一篇文章说得挺不错,它说:

  • 繁多职责是所有设计准则的根底,开闭准则是设计的终极目标。
  • 里氏替换准则强调的是子类替换父类后程序运行时的正确性,它用来帮忙实现开闭准则。
  • 而接口隔离准则用来帮忙实现里氏替换准则,同时它也体现了繁多职责。
  • 依赖倒置准则是过程式编程与面向对象编程的分水岭,同时它也被用来领导接口隔离准则。

简略地说:依赖倒置准则通知咱们要面向接口编程。当咱们面向接口编程之后,接口隔离准则和繁多职责准则又通知咱们要留神职责的划分,不要什么货色都塞在一起。当咱们职责捋得差不多的时候,里氏替换准则通知咱们在应用继承的时候,要留神恪守父类的约定。而下面说的这四个准则,它们的最终目标都是为了实现开闭准则。

参考资料

  • 写了这么多年代码,你真的理解 SOLID 吗?– 知乎
  • 如何了解 SOLID 准则?– ThoughtWorks 洞见
  • 重构的七宗罪 – ThoughtWorks 洞见
退出移动版