面向对象的几个重要原则

Mr.毛讲过:实践是检验真理的唯一标准!

还一句,理论指导实践。

对于面向对象开发来说,有那么几个原则需要了解、理解、实践中反思和践行。

PS.面试的时候也经常问到哟

一般来说,有两种版本,五大原则或者六大原则,分别是:

  • 单一职责原则(SRP,Single Responsibility Principle)
  • 开放封闭原则(OCP,Open Closed Principle)
  • 里氏替换原则(LSP)
  • 接口隔离原则(ISP,Interface Segregation Principle)
  • 依赖倒置原则(DIP,Dependence Inversion Principle)

以及

  • 迪米特原则(LOD)

前面五大原则直接记住首字母即可:SOLID,后一个我们单说。

单一职责原则(SRP)

  • 定义:单一职责原则(SRP:Single responsibility principle)又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。
  • 核心目的:解耦和增强内聚性
  • 难点:职责的划分;

SRP主要想约束的是类的高内聚低耦合,就好像开发中经常规定的一样,一个文件一般就只能有一个类(外部类)一样。

难点就在于单一职责的确定。

什么是职责?

有多种解释,我比较理解,或者说认可的是:

  • 所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。

  • 职责是与业务挂钩的;

都没有错,但是都比较抽象。

理解了一下,感觉是这样子的:

  • 职责应灵活多变,根据不同情境来变换;
  • 根据业务场景的当前和未来决定颗粒度;
  • 考虑单元测试用例情况,尝试逆向考虑;

也就是说,我比较认可动态定义职责,具体方法或角度参见上面几条。

开放封闭原则(OCP)

  • 定义:对于扩展是开放的,对于修改是关闭的
  • 核心目的:确保可拓展性
  • 难点:抽象;

对于开闭,实际开发中用的很多,毕竟这个坑还是踩过不少的。

难点,也是其实现方法中,抽象,具体是说:

实现开闭原则的关键就在于“抽象”。把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规定出所有的具体实现必须提供的方法的特征。作为系统设计的抽象层,要预见所有可能的扩展,从而使得在任何扩展情况下,系统的抽象底层不需修改;同时,由于可以从抽象底层导出一个或多个新的具体实现,可以改变系统的行为,因此系统设计对扩展是开放的。

我们在软件开发的过程中,一直都是提倡需求导向的。这就要求我们在设计的时候,要非常清楚地了解用户需求,判断需求中包含的可能的变化,从而明确在什么情况下使用开闭原则。

也就是说,在需求开发的初始阶段和维护阶段,都要有遵循开闭原则的意识,尤其是前者。

里氏替换原则(LSP)

  • 定义:任何基类可以出现的地方,子类一定可以出现。
  • 核心目的:使代码动态的同时,保持封闭性
  • 难点:子类父类的严格约束;

LSP约束了复用时的规则,说起来有几条吧:

  • 父类可以使用的地方,子类都应该可以替换父类,反之不行;
  • 子类应严格实现父类中的所有方法;
  • 子类的前置条件应该大于等于父类的;
  • 子类的后置条件应该小于等于父类的;

很难理解...

其实关键点在于什么是同一个类,什么是前后置条件。

先看下这个著名的例子:也谈"正方形不是长方形";

也就是说,在程序中来看,正方形不是长方形!!

按照常识,正方形是长方形的特例,如果对应到代码中,做特殊处理后,会直接破坏类的封闭性!!!

所以说,严格限制同一个类,父子类,是开发和封闭同时存在的重要因素。

另外前后置条件的约束,是在继承并没有复写的前提下的(我的理解,可能不对),具体看下这两篇文章吧:设计模式六大原则(2):里氏替换原则,六大设计原则之里氏替换原则

关于替换原则的使用问题,由于一般继承中,对于Override使用良多,还没有特别深刻的体会,欢迎大家提出意见。

接口隔离原则(ISP)

  • 定义:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
  • 核心目的:约束接口的颗粒度
  • 难点:如何确定接口是否拆分;

这个比较直接,就是大接口的拆分,或者开发开始时接口的定义。

举例:设计原则之接口隔离原则(ISP)

依赖倒置原则(DIP)

  • 定义:程序要依赖于抽象接口,不要依赖于具体实现。或者:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象
  • 核心目的:解耦
  • 难点:架构;

这一段说的很透彻:

问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

依赖倒置原则的核心思想是面向接口编程

所谓倒置,也就是这么个意思:高层不直接依赖模块,应依赖接口,模块实现接口;

设计模式六大原则(3):依赖倒置原则

迪米特原则(LOD)

  • 定义:迪米特法则(Law of Demeter)又叫作最少知道原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD
  • 核心目的:高内聚

类似单一职责等原则,强调高内聚

广义的迪米特法则在类的设计上的体现:

优先考虑将一个类设置成不变类。

尽量降低一个类的访问权限。

谨慎使用Serializable。

尽量降低成员的访问权限。

感觉使用中还是用的少。

总结

  • OOP核心要:高内聚,低耦合;
  • 设计抽象化;
  • 面向接口编程;
  • 拓展开发,修改关闭
posted @ 2018-04-03 15:28  韧还  阅读(1368)  评论(0编辑  收藏  举报