点击蓝色 “陈树义” 关注我哟

说到 SOLID 原则,相信有过几年工作经验的朋友都有个大概印象,但就是不知道它具体是什么。甚至有些工作了十几年的朋友,它们对 SOLID 原则的理解也停留在表面。今天我们就来聊聊 SOLID 原则以及它们之间的关系。

01 什么是 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 提倡使用者依赖一个抽象的服务接口,而不是去依赖一个具体的服务执行者,从依赖具体实现转向到依赖抽象接口,倒置过来。

02 SOLID 原则的本质

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

  • 单一职责是所有设计原则的基础,开闭原则是设计的终极目标。

  • 里氏替换原则强调的是子类替换父类后程序运行时的正确性,它用来帮助实现开闭原则。

  • 而接口隔离原则用来帮助实现里氏替换原则,同时它也体现了单一职责。

  • 依赖倒置原则是过程式编程与面向对象编程的分水岭,同时它也被用来指导接口隔离原则。

图片 l 来自 ThoughtWorks

简单地说:依赖倒置原则告诉我们要面向接口编程。当我们面向接口编程之后,接口隔离原则和单一职责原则又告诉我们要注意职责的划分,不要什么东西都塞在一起。

当我们职责捋得差不多的时候,里氏替换原则告诉我们在使用继承的时候,要注意遵守父类的约定。而上面说的这四个原则,它们的最终目标都是为了实现开闭原则。

参考资料

  • 写了这么多年代码,你真的了解 SOLID 吗?- 知乎

  • 如何理解 SOLID 原则?- ThoughtWorks 洞见

  • 重构的七宗罪 - ThoughtWorks 洞见


推荐阅读

  • 为什么要学设计模式:本质、价值与收益

  • 架构师必读:日均 500 万数据,如何进行数据存储选型?

  • 效率工具:如何快速复制页面标题和链接?

  • 有赞 CTO 崔玉松:我想打造出中国最好的技术团队

  • 技术 Leader 一定要懂所有业务细节吗?

  • 消失的这一个月,我都做了些啥?

  • 做了两年技术 Leader,聊聊我的技术管理思考

  • 我双十一省了一个亿,聊聊我的购物消费观

  • 时间真的就像海绵里的水,挤挤就会有吗?

公众号 @陈树义,用最简单的语言,分享我的技术见解。

超易懂!原来 SOLID 原则要这么理解!相关推荐

  1. 超易懂,原来SOLID原则要这么理解

    说到 SOLID 原则,相信有过几年工作经验的朋友都有个大概印象,但就是不知道它具体是什么.甚至有些工作了十几年的朋友,它们对 SOLID 原则的理解也停留在表面.今天我们就来聊聊 SOLID 原则以 ...

  2. 我所理解的SOLID原则

    S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写. 面向对象设计的原则 SRP     The Sing ...

  3. OOD: 我所理解的SOLID原则

    S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写. 面向对象设计的原则 SRP  The Single ...

  4. 什么是SOLID原则(第3部分)

    让我们从最后一个 SOLID 原则开始吧,即依赖倒置原则(Dependency Inversion Principle,简称 DIP)(不要和依赖注入Dependency Injection ,DI ...

  5. 独家 | Python中的SOLID原则(附链接)

    作者:Mattia Cinelli翻译:朱启轩校对:欧阳锦本文约3500字,建议阅读15分钟本文通过一些Python示例代码介绍了可以提高代码可靠性的SOLID编码准则. 标签:数据结构,编程,数据科 ...

  6. 每个Web开发者都应该知道的SOLID原则

    原创: 前端之巅 前端之巅 10月20日 作者|Chidume Nnamdi 编辑|谢丽 面向对象的编程并不能防止难以理解或不可维护的程序.因此,Robert C. Martin 制定了五项指导原则, ...

  7. 设计模式之SOLID原则再回首

        本科阶段学过设计模式,那时对设计模式的五大原则--SOLID原则的概念与理解还是比较模糊,此时过去了2年时间,在学习<高级软件工程>课程中老师又提到了设计模式,课程中还详细讨论了五 ...

  8. SOLID 原则的可靠指南

    山姆·米灵顿 读完需要 7 分钟 速读仅需 3 分钟 山姆是牛津大学的一名软件开发人员,目前在生物信息学领域工作.山姆的专长领域是 Java,他每天都在为生物学研究编写多线程分析工具.他是一位热情的程 ...

  9. 实践GoF的23的设计模式:SOLID原则(下)

    本文分享自华为云社区<实践GoF的23的设计模式:SOLID原则(下)>,作者: 雷电与骤雨. 在<实践GoF的23种设计模式:SOLID原则(上)>中,主要讲了SOLID原则 ...

最新文章

  1. SAP SD基础知识之组织结构
  2. %fplot('Untitled1',[-1,2])画图
  3. 20162318 2018-2019-2《网络对抗技术》Exp1 PC平台逆向破解
  4. 分布式资源调度—YARN框架
  5. celery mysql 异步_celery配合rabbitmq任务队列实现任务的异步调度执行[celery redis]
  6. Oracle 常用命令举例
  7. java中sql语句怎么把开始和结束时间作为参数写sql查询_聊一聊MyBatis 和 SQL 注入间的恩恩怨怨
  8. 成为谷歌的java程序员首先要做到这五点!
  9. asp.net 数据绑定 使用eval 时候报 “字符文本中的字符太多” 问题解决
  10. Mysql实现企业级数据库主从复制架构实战
  11. Scihub永久链接
  12. paip.常用android手机软件----语音篇
  13. 基于RRT算法的路径规划
  14. 对民办幼儿园管理的几点思考
  15. VoLTE前台信令详析及注释说明
  16. iOS Socket 客户端 基本使用
  17. 一个人,可以走多远?
  18. 【Android 10 源码】healthd 模块 HAL 1.0 分析
  19. 你的时间都去哪了?(三)时间利用情况分析报告(柳比歇夫时间统计法)
  20. oracle远程不能访问权限,oracle限制远程访问

热门文章

  1. 【附源码】计算机毕业设计SSM网上化妆品商城设计
  2. 计算机组成原理与汇编语言实验与课设源码分享
  3. SSM框架的流程及优点
  4. 瑞银看淡未来中国3G业务发展
  5. Home /Hometown
  6. 软件推荐(可免费试用)
  7. 网站漏洞扫描注入批量getshell工具
  8. php第三方支付接口视频,接入第三方支付及回调
  9. git学习----3.21 未完待续
  10. 前端学习之vue的生命周期函数