热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

六大原则单一职责原则里氏替换原则依赖倒置原则接口隔离原则迪米特法则开闭原则前言设计模式不容易用文字描述清楚,而过多的代码,看起来也让人摸不到头

六大原则

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

 

前言

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

 

单一职责原则

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

 

里氏替换原则

一位姓里的女士提出来的,所以叫里氏替换原则。  通俗解释此原则: 子类可以扩展父类的功能,但不能改变父类原有的功能。  它包含以下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 something
  return this;
}
Tyre(){
  //to do something
  return this;
}
Sofa(){
  //to do something
  return 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 something
  return this;
}
Tyre(){
  //to do something
  return this;
}
Sofa(body){
  //to do something
  return 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状态的判断。  开闭原则的扩展开放,修改封闭,是很有必要的。

 

此系列博客目录:

 

子慕谈设计模式系列(一)

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

子慕谈设计模式系列(三)

 


推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 一、Struts2是一个基于MVC设计模式的Web应用框架在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2优点1、实现 ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
author-avatar
湛蓝天空jk
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有