面向对象设计原则

1,501 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第31天,点击查看活动详情

👨‍🎓作者简介:一位喜欢写作,计科专业的大三菜鸟

🏡个人主页:starry陆离 的个人主页

如果文章有帮到你的话记得点赞👍+收藏💗支持一下哦

1.面向对象设计原则概述

软件的可维护性(Maintainability)可复用性(Reusability) 是两个非常重要的用于衡量软件质量的属性。

面向对象设计的目标之一在于支持可维护性复用,一方面需要实现设计方案或者源代码的复用,另一方面要确保系统能够易于扩展和修改,具有良好的可维护性

  • 可维护性(Maintainability) :指软件能够被理解、改正、适应及扩展的难易程度
  • 可复用性(Reusability) :指软件能够被重复使用的难易程度

出于这个目标面向对象设计原则应运而生,七大设计原则作为我们的开发指导。遵循七大原则来开发设计虽不是一种必需品,但是一名合格的程序员的自我要求

意义:

  • 面向对象设计原则为支持可维护性复用而诞生
  • 指导性原则,非强制性原则
  • 每一个设计模式都符合一个或多个面向对象设计原则,面向对象设计原则是用于评价一个设计模式的使用效果的重要指标之一

并没有完美的设计模式,至今没有一个设计模式同时满足七大原则。

模式名称 使用频率
单一职责原则 (Single Responsibility Principle, SRP)一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中★★★★☆
开闭原则 (Open-Closed Principle, OCP)软件实体应当对扩展开放,对修改关闭★★★★★
里氏代换原则 (Liskov Substitution Principle, LSP)所有引用基类的地方必须能透明地使用其子类的对象★★★★★
依赖倒转原则 (Dependence Inversion Principle, DIP)高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象★★★★★
接口隔离原则 (Interface Segregation Principle, ISP)客户端不应该依赖那些它不需要的接口★★☆☆☆
合成复用原则 (Composite Reuse Principle, CRP)优先使用对象组合,而不是继承来达到复用的目的★★★★☆
迪米特法则 (Law of Demeter, LoD)l每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位★★★☆☆

2.单一职责原则

单一职责原则是最简单的面向对象设计原则,用于控制类的粒度大小

单一职责原则是实现高内聚、低耦合的指导方针。如果一个类担任的职能太多,那么一个职能的变化极大的可能也会影响其他职能的运作。所以通过单一职责原则我们将不同的职能分离到不同的类中;不过这样类的数量变多了,会降低系统的效率。

3.开闭原则

开闭原则是面向对象的可复用设计的第一块基石,是最重要的面向对象设计原则

在开闭原则的定义中,软件实体可以是一个软件模块、一个由多个类组成的局部结构或一个独立的类

开闭原则是指软件实体应尽量在不修改原有代码的情况下进行扩展

  • 抽象化是开闭原则的关键
  • 相对稳定的抽象层 + 灵活的具体层
  • 对可变性封装原则(Principle of Encapsulation of Variation, EVP) :找到系统的可变因素并将其封装起来

4.里氏代换原则

里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士、麻省理工学院教授Barbara Liskov和卡内基.梅隆大学Jeannette Wing教授于1994年提出

在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象

在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型

例如我喜欢动物可以推出我喜欢狗,因为狗是动物的子类(狗是在动物这个范畴里)

父类:动物 子类:狗 向下转型:动物=new 狗(安全)

父类:狗 子类:动物 向下转型:狗=new 动物(不安全、错误)

5.依赖倒转原则

依赖倒转原则是Robert C. Martin在1996年为“C++ Reporter”所写的专栏Engineering Notebook的第三篇,后来加入到他在2002年出版的经典著作 《Agile Software Development, Principles, Patterns, and Practices》 一书中

开闭原则是目标,里氏代换是基础、依赖倒转是手段

依赖倒转原则指导我们要针对接口编程,不要针对实现编程

在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等

针对抽象层编程,将具体类的对象通过依赖注入(Dependency Injection, DI) 的方式注入到其他对象

  • 构造注入
  • 设值注入(Setter注入)
  • 接口注入

6.接口隔离原则

广义“接口”定义:一个类型所提供的所有方法特征的集合。一个接口代表一个角色,每个角色都有它特定的一个接口 “角色隔离原则”

狭义“接口”定义:狭义的特定语言的接口。接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口,每个接口中只包含一个客户端所需的方法 “定制服务”

  • 当一个接口太大时,需要将它分割成一些更细小的接口
  • 使用该接口的客户端仅需知道与之相关的方法即可
  • 每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干

7.合成复用原则

合成复用原则又称为组合/聚合复用原则(Composition/ Aggregate Reuse Principle, CARP)

  • 合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分
  • 新对象通过委派调用已有对象的方法达到复用功能的目的
  • 复用时要尽量使用组合/聚合关系(关联关系),少用继承

继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用 )

组合/聚合复用:耦合度相对较低,有选择性地调用成员对象的操作;可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。(“黑箱”复用 )

复用多用关联关系,扩展多用继承关系。

继承的一些缺点:

  1. 局限性,如Final类不可继承
  2. 继承是一种静态关系,不能动态的修改父类。
  3. 破环封装性,继承复用会暴露父类的所有方法给子类。

如下这个例子,ClassA2关联复用了ClassA中的m1()方法,是通过一个将ClassA的对象为了ClassA2的成员变量达到复用,而ClassA3继承复用了ClassA的m1()方法,直接通过super.m1()即可达到复用。

image-20220917145821703

8.迪米特法则

迪米特法则又称为最少知识原则(Least Knowledge Principle, LKP)

迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目

迪米特法则要求一个软件实体应当尽可能少地与其他实体发生相互作用

应用迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系

  • 迪米特法则要求在设计系统时,应该尽量减少对象之间的交互
  • 如果两个对象之间不必彼此直接通信,那么这两个对象就不应该发生任何直接的相互作用
  • 如果其中一个对象需要调用另一个对象的方法,可以通过“第三者”转发这个调用
  • 通过引入一个合理的“第三者”来降低现有对象之间的耦合度