【面向对象】面向对象七大原则

发布于:2025-08-06 ⋅ 阅读:(11) ⋅ 点赞:(0)

设计模式

设计模式是什么?

设计模式是一种针对于反复提出问题的解决方案,是经过长时间经验和试错而总结出来的一套业务流程;

其目的是为了提高代码的可重用性和可维护性,让代码更容易让人理解,保证代码可靠性;

设计模式所担当的角色和位置

设计模式与算法和软件系统的架构所处在的位置不同

**算法:**是为了解决一套困难及问题所完成的,是一系列解决问题的清晰指令;

**设计模式:**常见问题的通用思想/模版,一种高程度的抽象概念;

软件架构:在知道大型软件系统的各个方面的设计,是凌驾于设计模式之上的;

面向对象

面向对象是什么?

面向对象是一种结构化的编程方法,它通过封装、继承、多态和抽象等特性,使得代码更加模块化可扩展,并且更容易维护。

模块化思想,可扩展性的思维模式,就是设计模式的一种基本思想。

面向对象设计原则是学习设计模式的基础,设计模式的本质也便是面向对象设计原则

每种原则都包含着一些设计思想,可以从不同的角度去提升软件中的结构;在进一步前进,就是重构;

重构代码,力求在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。

UML

UML(统一建模语言)是系统设计中捋清思路所不可获缺的内容;

结构型图

  1. 类图(Class Diagram):系统中类的静态结构,包括类的属性、方法以及类与类之间的关系,如关联、继承、实现等。

  1. 对象图(Object Diagram):是类图的实例,展示类的对象在某一时刻的状态以及对象之间的关系

画板

  1. 组件图(Component Diagram):描述系统中组件的物理结构以及组件之间的依赖关系,组件可以是代码模块、可执行文件等。

画板

  1. 部署图(Deployment Diagram):显示系统的物理部署,包括硬件设备、软件组件在这些硬件上的部署情况。

  1. 包图(Package Diagram):用于组织和管理模型元素,展示包与包之间的依赖关系,包可以包含类、组件等元素。

画板

行为型图

  1. 用例图(Use Case Diagram):从用户的角度描述系统的功能,展示系统的参与者(用户或外部系统)与系统提供的用例(功能)之间的关系。

画板

  1. 活动图(Activity Diagram):描述系统中各种活动的执行流程,类似于流程图,可用于展示业务流程、算法步骤等。

  1. 状态图(State Diagram):展示对象在其生命周期内的各种状态以及状态之间的转换,适用于描述具有状态变化的对象行为

画板

  1. 序列图(Sequence Diagram):按时间顺序展示对象之间的交互,强调对象之间消息传递的顺序。

通信图(Communication Diagram):也用于展示对象之间的交互,但更侧重于对象之间的连接关系和消息传递,不强调时间顺序。

画板

UML类图的各种关系

  1. 关联(Association)
    • 含义:表示类之间的一种静态结构,是一种比较 “强” 的关系,比如对象 B 是对象 A 的成员对象。
    • 表示方式:通常用一条实线表示。 动物->鸟
  2. 继承(泛化,Generalization)
    • 含义:用于表示子类与父类之间的关系。子类拥有父类的所有功能。
    • 表示方式:用实线 - 空心箭头,箭头指向基类医生->人
  3. 实现(Realize)
    • 含义:是一种类与接口之间的关系。
    • 表示方式:用虚线 - 空心箭头,箭头指向接口。 手机 -> WiFi功能 <<实现>>
  4. 依赖(Dependency)
    • 含义:描述的是某个类的对象在运行期间,可能会用到另一个对象,这是一种比较 “弱” 的关系,不同于关联,比如类 A 成员方法的形参是类 B 的实例对象。
    • 表示方式:用虚线箭头表示。
  5. 组合(Composition)
    • 含义:是一种整体与部分的关系,且部分不能离开整体而单独存在,是一种比聚合还强的关系。
    • 表示方式:带实心菱形的实线,菱形指向整体。
  6. 聚合(Aggregation)
    • 含义:也是描述整体与部分的关系,且部分可以离开整体而单独存在,区别于组合。
    • 表示方式:带空心菱形的实心线,菱形指向整体

面向对象的七大设计原则

总述

  1. 面向对象设计原则
设计原则名称 设计验证简介
单一职责原则(Single Responsibility Principle,SRP) 类的职责要单一,不能把太多的职责放到一个类中;
开闭原则(Open-Closed Principle,OCP) 软件实体对扩展是开放的,但修改是关闭的。在不修改原来实体功能的基础上扩展功能;(代理模式
里式替换原则(Liskov Substitution Principle LSP) 在软件系统中,一个可以接受基类的地方必然要可以接受一个子类对象;
依赖倒转原则(Dependency Inversion Principle DIP) 只针对于抽象层编程,不要针对于具体的类编程;(DI 依赖注入
接口隔离原则(Interface Segregation Principle ISP) 使用多个专门的接口来取代一个统一的接口;
合成复用原则(Composite Reuse Principle CRP) 在复用功能时,应当尽量多的使用组合和聚合关系,尽量少的使用继承关系;
迪米特法则(Law of Demeter LoD) 一个软件实体对其他实体的引用越少越好,或者说两个类不必彼此之间直接通讯,那么这两个类不应当直接发生相互作用,而是引入第三者进行直接交互;

「设计模式」理解面向对象的七大设计原则,消除代码中的坏味道

单一职责原则

定义:
  1. 单一职责原则是最简单的面向对象设计原则,用于控制类粒度的大小;它建议一个对象应该只包含单一的职责。
  2. 一旦某个类承担的职责越多,相当于这些职责被耦合在同一个类中,所以它能被复用的可能性就越小
  3. 单一职责原则旨在实现高内聚、低耦合,在很多代码重构的手法中都能见到它的身影,虽然思想简单但是却最难运用,因为只有当你有较强的分析能力后,才能将一个耦合度极高的类分离成几个单独的职责明确的类。
分析
  1. 原始方案

  • 实现功能:初始化页面、获取数据库连接、根据账号密码获取User数据
  1. 重构方案

  • 拆离所有的业务逻辑,封装内容,只保留一个main方法;
  • 拆分一:在main中导入LoginForm.class执行,后续逻辑,初始化功能的逻辑在该Class的执行;;
  • 拆分二:分析业务结构,关联数据库,查询数据,封装数据库持久层的内容UserDao,只提供数据 ,进行查询,不进行DB交互;
  • 拆分三:DB交互功能单独封装一个实体类:DButil

开闭原则

定义

一个软件实体应当对扩展开放,对修改关闭。也就是说设计的模块能在不被修改源代码的前提下,对其进行扩展。

开闭原则的切入点:将系统的可变因素封装并抽象到上层。

分析
  1. 原始方案

  • 问题点: 如果界面类 LoginForm 需要将圆形按钮 CircleButton 改为矩形按钮 RectangleButton,那么不仅要修改 LoginForm 中的按钮类的名字,还要修改 display() 方法适配修改后的按钮。
  1. 重构方案

  • 根据开闭原则,抽离出来AbstractButtonCircleButtonRectangleButton 都继承AbstractButton的功能,然后在追加自己的内容;
  • 通过配置文件或者set注入的方式,在LoginForm想要增加新的的按钮;
  • 修改功能也是同样的道理,使用一个切入点切入,在不动根本的情况下修改之前的内容;
  • CircleButtonRectangleButton 都继承AbstractButton的功能,所以可以使用AbstractButton的原则上也可以使用继承自他的对象;

里式替换原则

定义
  1. 里氏替换原则要求所有引用基类(父类)的地方必须透明地使用其子类的对象。
  2. 开闭原则的核心是对系统进行抽象化,并且从抽象化导出具象化从抽象化到具象化的过程则需要里氏替换原则的指导。
分析
  1. 原始方案

在实现开闭原则的前提下,需要再对其枪械的种类进行扩展,扩展玩具手枪 ToyGun 类的原始设计方案如下:

  1. 引用里氏替换原则后

  • ToyGun虽然 继承于Gun,但是是不能执行 killEnemy的,所有是有问题的,不符合 里式替换原则
  • ToyGun抽象出来,聚合到 Toy下;
  • Soldier完成 kellEnemy是需要依赖 Gun而不是Toy

依赖倒转原则

定义

简单理解依赖倒转原则,就是要针对接口编程,不要针对实现编程。

依赖倒转原则旨在通过对接口编程颠覆传统的过程型系统:高层依赖低层。其实就是<font style="color:rgb(37, 43, 58);">IOC</font>的控制反转,可以联系Spring的IOC依赖注入的方式。

分析
  1. 原始

  • 写一个Driver执行BenzBMW
  • 加一个Audi需要再写一个Audi;长期以往;
  1. 使用依赖倒转原则

  • 反正实行的都是ICar,使用的一样的方法,抽象出来ICar接口,IDriver执行ICar
  • BenzBMW实现 ICar
  • Driver实现 IDriver
  • 以上,我们就不需要反复修改我们的代码逻辑,只需要通过注入的方式就可以;譬如,注入BMWDriver执行BMW

接口隔离原则

定义
  1. 使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
  2. 在进行系统设计时采用定制服务的方式,即为不同的客户端提供宽窄不同的接口,只提供用户需要的行为,而隐藏用户不需要的行为。
分析
  1. 原方案
  2. 对应后方案
  • 无论是使用一个实现类(如图)还是三个实现类,对于 ClientA 都只能访问它对应的方法,无法访问与之业务无关的另外两个,因为它们是针对抽象接口进行编程的。

合成复用原则

定义

尽量使用对象组合,而不是继承来达到复用的目的

合成复用原则(又称组合 / 聚合复用原则)也是一条相当重要的原则,为了降低系统中类之间的耦合度,该原则倡导在复用功能时多使用关联关系,少使用继承关系

对比一下两种复用机制:

  • 继承复用:子类只需覆盖父类方法,但关键问题在于父类的内部细节完全暴露给子类,破坏系统的封装性;而且一旦超类发生变化,子类也不得不随之变化,使得子类不够灵活
  • 组合 / 聚合复用:由于组合 / 聚合是将其他对象 B 转化为某对象 A 的成员对象,所以其他对象 B 的内部实现细节对于 A 是完全不可见的,实现了系统的封装性。

链接:https://juejin.cn/post/7072521978095075358

分析
  1. 原方案
  • DBUtil用于连接数据库,提供getConnection()方法连接DB获取数据库返回对象,两个实体类都需要调用DBUtil.getConnection();,使用继承实现的话 这样就会出现继承服用;
  • 在执行时,会调用DBUtil.getConnection();,内容的所有数据库配置等参数都会被执行时发现;
  • 如果更改可连接方式或者数据池,原来的DBUtil的方法就不能够被使用了,我们就只能在写一个DBUtilR,或者继承之后再重写一遍DBUtil的方法,这样就违背了前面的开闭原则,整个逻辑就会显得扩展性极差
  1. 修改后
  • 图解的我理解的不是很清晰,简要概述下自己的思想
  • 不要多用继承,可以在所需的方法中,将要实现的类 引用为所需要的对象,以防止出现上述我所描述的那种现象;
  • 在DBUtil中,我们创建 JDBCUtil方法,PoolUtil方法,··· ··· 在使用DBUtil时,我们不用继承,在方法中New一个新的对象DBUtil,或者new一个继承自DBUtilJDBCUtil/PoolUtil,将其作为我们持久层方法的一个引入的对象,这样我们再进行使用的时候,只会对我们只调用的自己的方法进行使用;

迪米特法则

定义

迪米特法则指导软件实体应当尽可能少地与其他实体发生相互作用。

又称最少知识原则,不要与不认识的人陌生对象,只自己相关的对象打交道;

朋友的定义范围:

  • 当前对象本身
  • 依赖对象(以参数形式传出成员方法的对象)
  • 当前对象的成员对象
  • 成员对象的元素(成员对象是集合的情况,元素可能也是对象)
  • 当前对象所创建的对象

当前对象与 ”陌生人“ 保持着微乎其微的依赖关系,它们之间仍是耦合状态,这就需要第三方对象 ”朋友“ 来转发调用二者之间的通信,以降低系统的耦合度,使得类与类之间保持松散的耦合关系;但同时也会降低系统不同模块间的通信效率。高内聚,低耦合 很重要!!

分析
  1. 原方案

public class Someone {
    private Stranger stranger = new Stranger();
    
    public void operation1(Friend friend) {
        Stranger stranger = friend.provideStranger();
        stranger.operation3();
    }
}

Someone 类需要调用 ”陌生人“ 的 operation3() 方法,于是通过 ”朋友“ 的 provideStranger() 方法获取到了 Stranger 对象,然后调用该对象的 operation3()

看似很常见且合理,因为平常我们也是像这样编写代码的,但常见就一定合理吗?其实 SomeoneStranger 之间存在一层弱弱的依赖关系,因为 Friend 提供的 Stranger 对象还是在 Someone 方法中进行了调用,仍然存在耦合,违背了迪米特法则的指导思想

  1. 重构方案

public class Someone {
    public void operation1(Friend friend) {
        friend.forward();
    }
}


public class Friend {
    private Stranger stranger = new Stranger();
    
    public void forward() {
        stranger.operation3();
    }
}

通过 Friend 的转发调用,使得 SomeoneStranger 解耦,一旦 Stranger 被替换掉,也不会影响 Someone 源代码的修改。

以上 针对于「设计模式」理解面向对象的七大设计原则,消除代码中的坏味道 的学习,思维模型的学习,受用无穷。


网站公告

今日签到

点亮在社区的每一天
去签到