六大原则

  • 单一职责原则
  • 里氏替换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特法则
  • 开闭原则

前言

设计模式不容易用文字描述清楚,而过多的代码,看起来也让人摸不到头脑,加上词语或者文字描述的抽象感,很容易让人看了无数设计模式的文章,也仍然理解不了。 所以我一直打算写此系列博客,首先我会从大量文章里去理解这些设计模式,最后我用自己的语言组织转化为博客,希望用更少的代码,更容易理解的文字,来聊一聊这些设计模式。 我所理解、所描述的每一个设计模式也可能有些是错误的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客内容。 因为我是前端,所以设计模式的代码以前端代码和视角为主。 此博客内容对每一种模式并不会写得非常深入,也许能为读者打通一些认知,如果看了此系列博客,再去看其他更深入的博客,可能是一种比较好的方式。

单一职责原则

单一职责原则很简单,一个方法 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。 这是常识,几乎所有程序员都会遵循这个原则。

里氏替换原则

一位姓里的女士提出来的,所以叫里氏替换原则。 通俗解释此原则: 子类可以扩展父类的功能,但不能改变父类原有的功能。 它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

① 子类可以实现父类的抽象方法,但不能覆盖(重写)父类的非抽象方法

方法覆盖又称方法重写,所以我理解这里的覆盖就是重写吧。 当子类继承了父类,有些情况下可能还是需要重写继承的方法。 但是重写确实会给系统造成一些麻烦,特别是重写的次数变多了之后,后期维护或者迭代的过程中容易概念混淆,逻辑混淆,加大犯错的风险。父类的方法应该尽量稳定。

② 子类中可以增加自己特有的方法。

子类继承父类,肯定是需要子类有自己特有方法的,否则就没必要继承了。

③ 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

输入的参数更宽松可以理解为“更大的范围或者定义”, 父类定义的入参要足够宽泛,覆盖子类需求,子类的参数应该在父类定义的范围内,但是当父类方法不能满足子类的情况下,出现重载或者重写,这时候输入的参数不放大的话,是没法满足业务需求的 。

有一个例子:鸵鸟不是鸟。 按照鸟的定义鸵鸟确实是鸟(恒温动物,卵生,全身披有羽毛,身体呈流线形,有角质的喙,眼在头的两侧。前肢退化成翼,后肢有鳞状外皮,有四趾),那么鸵鸟继承于鸟这个基类,印象中鸟都是能飞的,所以鸟类定义一个飞行速度参数。 因为鸵鸟不能飞,就只能把速度定义为0。 现在出现一个业务,需要计算每个鸟类飞过黄河的时间,可是鸵鸟速度为0,时间永远无法得出,这个就造成业务无法顺利进行了,鸵鸟不能完全替代鸟,这也就违背了里氏替换原则。 所以这里不能直接用鸟类来计算飞行时间,而是应该删除鸟类的飞行速度参数,生成一个子类:飞鸟类,给飞鸟类定义飞行速度,去计算所有飞鸟类的飞行时间,这样才能满足需求。

需要注意两点,1对类的继承关系的定义要搞清楚,2设计要依赖于具体行为和环境。总之子类可以随便扩展,但是别改父类。

④ 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

JS不需要定义抽象方法,返回值也是不用申明的。 对于java来说,抽象方法只需要定义,方法体为空,当然也没必要申明返回,这个规则对java来说自然就已经遵循了。

依赖倒置原则

  • 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
  • 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
① 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。

先说说什么是高层模块什么是底层模块。 高层模块调用底层模块,被另一个模块调用的模块就叫底层模块。在传统的应用架构中,低层次的组件设计用于被高层次的组件使用,这一点提供了逐步的构建一个复杂系统的可能。 在这种结构下,高层次的组件直接依赖于低层次的组件去实现一些任务,这种对于低层次组件的依赖限制了高层次组件被重用的可行性。 而依赖倒置原则使得高层次的模块不依赖于低层次的模块的实现细节,把高层次模块从对低层次模块的依赖中解耦出来,从而使得低层次模块依赖于高层次模块的需求抽象,当高层模块需要使用底层模块,便引用此底层模块。 回想一下angular的依赖注入,就是这种。 下面第二条的例子也可以帮助更好的理解。

② 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
具体实现依赖于抽象,举个例子,造一辆车,需要车架,轮子,沙发等。先搭好架子,定义一个具体实现类,安装车架,放置沙发,安装轮胎。具体实现流程已经具备,并且也定义好了抽象方法。
Car(){var body = new Body();var tyre = new Tyre();var sofa = new Sofa();this.setBody(body);this.setTyre(tyre);this.setSofa(sofa);return this;
}复制代码

那么接下来就是造车架、造轮子、造沙发。
Body(){//to do somethingreturn this;
}
Tyre(){//to do somethingreturn this;
}
Sofa(){//to do somethingreturn this;
}复制代码

反过来,抽象依赖于具体实现,举个例子(把上面的改造一下):造一辆车,先造个轮子,根据轮子再去做个车架,根据车架再去完成沙发。

Car(){var tyre = new Tyre();this.setTyre(tyre);var body = new Body(tyre);this.setBody(body);var sofa = new Sofa(body);this.setSofa(sofa);return this;
}
Body(tyre){//to do somethingreturn this;
}
Tyre(){//to do somethingreturn this;
}
Sofa(body){//to do somethingreturn this;
}复制代码

这样的话,如果轮胎变化了,可能就会影响整个后面流程。 总结一下:具体实现依赖抽象,就是提前定义和约定好每个抽象,然后分别去实现抽象,再去具体实现,做到心中有数,各个击破。 而抽象依赖具体实现,就是先做一个事情,再考虑下一个事情,没有提前规划完善,过程中就容易出问题。

接口隔离原则

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 这个原则很简单,盗用两个图如下,对于java等语言来说,B和D类依赖“接口I“(图1),但是B并没有使用方法4、5,D没有使用方法2、3,但是因为依赖接口I,所以也需要定义并不使用的方法。 从前端角度来说,一个公共类它可能被不同的其它类引用,但是每个类只需要用到公共类的其中一个方法,但是却需要把公共类全部引入,这样就显得太臃肿。 所以通过把一个大接口拆分成几个小接口,可以使代码更精准,引用更灵活。 接口可以尽量小,但是要有限度。 对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化,所以一定要适度。

迪米特法则

迪米特法则又叫作最少知道原则,就是说一个对象应当对其他对象有尽可能少的了解。 举个例子:我们定义一个类,定义一个变量,会使用get,set方法来控制这个变量读写代码如下:

var obj = {val:0,setVal:function(val){this.val = val;alert(val);},getVal:function(val){return this.val;}
}function xxx(){obj.setVal(666);
}复制代码

如果我们需要在修改val值的时候,弹出一个提示框告诉我们最新的值,那么我们可以把alert写在setVal方法内,在其他对象中使用obj对象的时候,只需要使用setVal方法,obj内部发生了什么当前类并不知道,当前类只能用暴露出来的set方法,而不需要知道set方法做了什么事情。 反过来,我们抛弃这个原则写一个代码例子:

var obj = {val:0
}function xxx(){obj.val = 666;alert(obj.val);
}复制代码

这样xxx方法就非常了解obj这个对象了,因为在它内部直接操作了obj对象。真的如此就一点封装都没有了,在其他地方也会出现非常多的重复代码。 通俗的来讲,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供公共方法,不对外泄漏任何信息。

开闭原则

软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。 在软件开发和迭代过程中,常常可能需要修改逻辑,比如一个公用的方法Func在不同地方被使用,在某一个方法体中需要修改这个公用方法的逻辑来达到当前的需求,但是修改此公用方法Func必然会影响到其他地方,然而其他地方需要保持原方法的逻辑,所以这里修改是被禁止的。 那么可以新增一个方法Func2,替换当前方法的引用,如此就是所谓的扩展,扩展是开放的。

再举一个常见的例子,对于订单数据,最开始我们定义了订单的状态status: 1-2分别代表 未完成和已完成。后来订单开始付费了,我们需要更多的状态,已支付和未支付。 那么支付状态和订单完成状态并不完全独立,要能同时表示用一个字段表示这两种状态,需要改变status的定义,比如1代表未完成并且未支付,2代表未完成并且已支付,如此修改之后,以前的所有判断和逻辑会发生变化,旧的数据也无法兼容,系统几乎很难迭代下去,代价也很大。 那么如果扩展一个字段payStatus,就不需要修改数据库定义,前端也只需要在以前的逻辑上多一个payStatus状态的判断。 开闭原则的扩展开放,修改封闭,是很有必要的。

此系列博客目录:

子慕谈设计模式系列(一)
子慕谈设计模式系列(二)——设计模式六大原则
子慕谈设计模式系列(三)

子慕谈设计模式系列(二)——设计模式六大原则相关推荐

  1. 《设计模式 系列》- 面向对象六大原则

    2019独角兽企业重金招聘Python工程师标准>>> 设计模式(Designpattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结. 一.单一职责原则 ...

  2. 设计模式(2)六大原则(七大原则)

    前言 前面已经明白了UML是什么,UML该在哪里用 接下来开始真正接触设计模式的思想:六大原则 目录 六大原则(七大原则) 开闭原则 2.1. 开闭原则的作用 2.2. 开闭原则的案例 里氏替换原则 ...

  3. 设计模式---设计模式的分类及六大原则

    设计模式 1.设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:   工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式. 结构性模型,共七种:   适配器模型,装饰器模型,代 ...

  4. 设计模式(一)——六大原则

    概念: 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用.设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案.这些解决方案是众多软件开发 ...

  5. 设计模式学习笔记:六大原则

    单一职责里氏替换依赖倒置接口隔离迪米特法则开闭 设计模式有六大法则. 1.单一职责 这好理解,每个类只干一样事情 2.里氏替换 子类可以扩展父类的功能,但不要修改父类的功能.意为: 1)子类可以实现父 ...

  6. 设计模式 之 设计的 六大原则(1)单一职责原则

    由于这些原则性东西 属于概念东西,就不具体以代码描述了.以下是摘自网上和自己的一些理解 首先了解一些 面向对象的特性: 面向对象 有 三大基本特征:封装 ,继承, 多态. 封装: 也就是把客观事物封装 ...

  7. 设计模式之——面向对象的六大原则

    一.单一职责原则     定义:Single Responsibility Principle,即SRP:一个类应该是相关性很高的方法与数据的封装,不同的职责应该分别封装到不同的类或模块当中.     ...

  8. 【设计模式系列】OO设计原则之LSP-Liskov替换原则

    概要 什么是所谓的替换原则?开发中我们通常会通过继承实现一些子类来达到功能的扩展,比如假设我们有基类B,有个B类型的指针或引用作为某个函数的参数,这时我们创建了子类C继承于B,如果当把指向C类对象的指 ...

  9. 设计模式(一)六大原则

    单一职责原则 里氏替换原则 依赖倒置原则 接口隔离原则 迪米特原则 开闭原则

最新文章

  1. UA MATH577 逻辑与可计算性1 递归函数
  2. java 自定义一个容器类
  3. win10必须禁用的服务_【亲测】Win10系统如何彻底禁止自动更新 亲测有效的Win10关闭自动更新方法...
  4. 关于标准输入输出流和string类
  5. matlab std函数_如何利用Matlab进行小波分析
  6. redis DB操作
  7. umijs 修改默认配置_UmiJS基础教程(2) 目录结构
  8. bzero等函数源代码实现
  9. 学习笔记(01):19年录制Zookeeper、Dubbo视频教程 微服务教程分布式教程 SpringBoot教程整合-技术选型和学后水平...
  10. TensorLy-神经网络张量库
  11. 神策分析 iOS SDK 全埋点解析之元素点击与页面浏览
  12. httpclient3与httpclient4不同版本使用方法
  13. python脚本-自动检测Base16、32、64、85系列编码、多层解码(新增base91解码)
  14. 程序员秃顶算工伤吗?
  15. js 计算两个时间的之间的天数
  16. 共阳极、共阴极数码管编码表(0~9、A~P……全亮)
  17. 什么是DOM(个人理解)
  18. 索引原理-索引数据结构
  19. 对html基础内容的理解
  20. CUDA 深入浅出谈[转]

热门文章

  1. Go:创建新进程(os.StartProcess源码解读)
  2. 基于Docker搭建Jumpserver堡垒机操作实践
  3. 新书问答:Agile Management
  4. LeetCode刷题(46)--Search in Rotated Array
  5. 第二:python安装校验报错api-ms-win-crt-process-l1-1-0.dll 丢失的处理
  6. python画猴子_Python学习笔记(1)
  7. python的网页解析器_网页解析器(BeautifulSoup)-- Python
  8. 苹果屏幕使用时间怎么设置_苹果手机屏幕不能旋转怎么办
  9. MyBatis3源码解析(8)MyBatis与Spring的结合
  10. o在linux是什么权限,Linux权限管理基本知识大全