当前位置: 首页 > news >正文

设计模式的七大原则

1.单一职责原则

单一职责原则(Single responsibility principle),即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1、A2。所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。

单一职责原则注意事项和细节

  1. 降低类的复杂度,一个类只负责一项职责。
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险
  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

2.接口隔离原则

​接口隔离原则(Interface Segregation Principle, ISP)是面向对象设计的五大原则之一,由Robert C. Martin在《Agile Software Development, Principles, Patterns, and Practices》一书中提出。这一原则强调了在设计软件时,应当尽量保持接口的小而专一,以避免客户端依赖它不需要的方法。

具体来说,接口隔离原则提倡以下几点:

  1. 模块化设计:每个接口应该专注于提供一组相关功能,而不是试图满足所有客户端的需要。这样可以确保接口的高内聚性,每个接口都只负责一个功能模块。

  2. 客户端定制接口:为不同的客户端或用途定义不同的接口,而不是强迫它们去依赖并实现那些它们实际上并不使用的接口方法。这样可以减少不必要的耦合,提高系统的灵活性和可维护性。

  3. 避免胖接口:胖接口是指包含了大量方法的接口,这往往意味着使用该接口的类需要实现很多它实际上并不关心的方法。ISP建议将这样的大接口拆分为更小、更具体的接口,使得每个类仅需实现它真正需要的方法。

遵循接口隔离原则的好处包括:

  • 提高代码的可读性和可维护性。
  • 减少代码间的耦合度,易于修改和扩展。
  • 提升系统的灵活性,便于单元测试。
  • 促进设计的清晰和职责的单一。

下面我将以Java语言为例,简单展示如何应用接口隔离原则。

假设我们有一个系统需要处理不同类型的动物,一开始可能会设计一个包含多种行为的胖接口

// 胖接口示例
interface Animal {void eat();void fly();void swim();
}

然后,有一个Duck类实现了这个接口:

class Duck implements Animal {@Overridepublic void eat() {System.out.println("Duck is eating.");}@Overridepublic void fly() {System.out.println("Duck is flying.");}@Overridepublic void swim() {System.out.println("Duck is swimming.");}
}

但问题在于,并非所有动物都能飞和游泳,比如鸟可能不能游泳,鱼不能飞。这导致了不必要的方法实现,如对于一个只能游泳的鱼来说,实现fly()方法是没有意义的。

按照接口隔离原则改进,我们可以将接口拆分为更具体的几个:

interface Eater {void eat();
}interface Flyer {void fly();
}interface Swimmer {void swim();
}

现在,每个类只实现它需要的接口: 

class Duck implements Eater, Flyer, Swimmer {// 实现相应的方法...
}class Fish implements Eater, Swimmer {// 实现相应的方法,无需实现fly()
}

这样,Fish类就不需要实现fly()方法,因为它不符合鱼的行为特征,这便是接口隔离原则的应用实例,它让我们的设计更加合理和灵活。 

3.依赖倒转原则

面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。

面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。

 4.里氏替换原则

在软件中,子类型(子类)必须能够替换其基类型(父类),并且在软件中使用基类型的地方,都能够使用子类型而不引发任何错误或导致程序的行为异常。这意味着子类应当能够保持父类的行为约定,不会破坏或违背父类的预期用途。

具体来说,里氏替换原则强调以下几个关键点:

  1. 行为一致性:子类应当能够替换父类,且不会影响到程序的正确性。即,使用父类的地方可以用子类替代,而不会引起任何错误或意外行为。

  2. 合约不变:子类应当遵守父类定义的接口契约(即方法的前置条件和后置条件不能比父类更严格)。例如,如果父类的一个方法承诺在某种输入下一定会返回一个正数,那么子类的相同方法也必须满足这一承诺。

  3. 增加功能但不改变原有行为:子类可以添加新的方法或属性,但不得去除或修改父类已有的方法(除非是通过重写使行为更为一般化,但仍需满足上述的合约不变原则)。

遵循里氏替换原则的好处包括:

  • 增强代码的稳定性:确保现有代码在新子类加入时仍能正常工作。
  • 提高代码的可预测性:开发者可以依赖基类的行为,而不用担心子类的具体实现细节。
  • 促进良好的设计:鼓励设计者在设计类的继承结构时更加谨慎,考虑接口和实现的一致性。

违反里氏替换原则可能导致的问题:

  • 子类的行为与父类不一致,可能在运行时引发错误。
  • 系统变得更加脆弱,难以维护和扩展,因为子类的修改可能会影响到依赖父类的其他部分。

基类 Shape.java

public abstract class Shape {public abstract double area();
}

子类 Rectangle.java

public class Rectangle extends Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double area() {return width * height;}
}

使用这些类的 Main.java

public class Main {public static void main(String[] args) {Shape shape = new Rectangle(10, 5); // 这里展示了里氏替换原则的应用System.out.println("Area: " + shape.area()); // 输出矩形的面积}
}

在这个Java示例中,Rectangle类继承了抽象类Shape并实现了area()方法。在Main类中,我们创建了一个Rectangle对象,并将其向上转型为Shape类型的引用。这证明了子类对象可以替换父类对象,同时程序逻辑保持正确,体现了里氏替换原则的精神。通过将Rectangle实例赋给Shape类型的变量,我们展示了子类对父类的透明替换能力,而无需修改使用Shape的现有代码。
父类可以不是抽象类吗?

父类不一定是抽象类。里氏替换原则并没有规定父类必须是抽象类。它可以是一个具体的类,只要子类遵循其规则即可。实际上,里氏替换原则适用于所有类型的继承关系,不论是抽象类还是具体类作为基类。

这里有一个非抽象父类的例子:

父类 Vehicle.java

public class Vehicle {public void start() {System.out.println("Vehicle started.");}
}

子类 Car.java

public class Car extends Vehicle {@Overridepublic void start() {System.out.println("Car started with ignition.");}
}

在这个例子中,Vehicle是一个具体类,它有一个start()方法。Car类继承了Vehicle类并重写了start()方法,提供了更具体的实现。只要Car类中的start()方法不违反Vehicle类中该方法的预期行为(比如,没有改变车辆启动的基本含义或引入不可接受的行为变更,即车开起来细化一下,可以是汽车,公交车,卡车开起来,但都比车这个范围更小更细化),这就遵循了里氏替换原则。即使Vehicle类不是抽象的,我们依然可以将Car实例安全地用在任何期待Vehicle类型的地方。

5.开闭原则 

开闭原则(Open Closed Principle)一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。也就是当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。用抽象构建框架,用实现扩展细节。

开始 Display直接修改成BarChart

  • Display:用于显示各种图表。
  • BarChart:一种特定类型的图表,表示条形图。

重构此设计以符合开闭原则。开闭原则要求软件实体(模块、类或方法)应该对扩展开放,对修改关闭。这意味着我们应该能够轻松添加新功能(如新的图表类型)而不必更改现有的代码。

重构后的类结构图:

  • Display:负责显示图表。
  • AbstractChart:一个新的抽象类,定义了显示图表的方法。
  • BarChart:继承自AbstractChart,表示条形图。
  • LineChart:同样继承自AbstractChart,表示折线图。
// 抽象类,定义了显示图表的方法
abstract class AbstractChart {abstract void display();
}// 条形图类
class BarChart extends AbstractChart {@Overridevoid display() {System.out.println("Displaying bar chart");}
}// 折线图类
class LineChart extends AbstractChart {@Overridevoid display() {System.out.println("Displaying line chart");}
}// 显示器类,负责显示图表
class Display {void display(AbstractChart chart) {chart.display();}

现在,如果需要添加新的图表类型(例如饼状图),只需创建一个新的类(如PieChart),让它继承自AbstractChart,并实现display()方法。然后,在Display类中,您不需要更改任何现有代码就可以处理新的图表类型。这就是开闭原则的一个简单应用。

6.迪米特法则

迪米特法则,也被称为最少知识原则(Least Knowledge Principle, LKP),是一种面向对象设计中的指导思想,它的核心理念非常直白:“不要和陌生人说话”。这句话听起来像是一句社交建议,但在编程世界里,它帮助我们构建低耦合、高内聚的软件系统。

想象一下,在一个公司里,员工们专注于自己的工作,他们通常只会直接和紧密相关的同事沟通,比如他们的直接上司、团队成员或是直接对接的其他部门同事。他们不会也不应该去干涉或者直接联系公司里每一个不认识的人。这样做的好处是,每个人的工作职责清晰,减少了不必要的干扰,提高了工作效率。

在编程中,迪米特法则应用这个思路,要求一个对象应该尽量减少与其他对象的直接交互,特别是避免和那些它不直接依赖的对象交流。具体来说,一个对象应该只和它的直接朋友通信:

  • 直接的朋友包括:对象本身、对象的成员变量(属性)、传递给对象的方法参数,以及由该方法创建或返回的对象。

遵循迪米特法则,我们可以这样做:

  1. 限制访问范围:尽量使用private或protected修饰符限制类成员的访问权限,只暴露必要的接口给外部。
  2. 减少依赖:通过接口而非具体类编程,减少类之间的直接耦合。
  3. 使用中介者:当需要跨多个层级访问数据时,可以引入一个中介对象来协调,避免高层级的对象直接操作低层级的对象。

7.合成复用原则

合成复用原则(Composite Reuse Principle)就是是尽量使用合成/聚合的方式,而不是使用继承。合成复用原则则更像是使用标准化的建筑模块来组装房屋。每个模块,比如卧室模块、厨房模块、浴室模块,都是独立设计和制造的。当你需要建造一座新房子时,不是从零开始或者修改旧设计,而是选择需要的模块进行组合。这样,即使未来想升级卧室设计或者替换浴室设施,只需替换相应的模块即可,不影响整体房屋结构的稳定性和其他部分的正常使用。

放到编程领域,这意味着我们应当尽量使用对象组合(即将对象作为其他对象的属性或通过集合管理)来实现新功能,而不是通过继承来扩展类。这样做能够使得系统更加灵活,易于维护和扩展,因为修改或添加功能时,不必触及或改动现有的类结构,只需调整或增加新的组件(对象)即可,保证了各个部分的独立性和复用性。就像是高效又灵活的建筑工地,随时可以根据需求调整设计方案,而不会造成大规模的返工。

相关文章:

设计模式的七大原则

1.单一职责原则 单一职责原则(Single responsibility principle),即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1、…...

ThreeJS-3D教学十五:ShaderMaterial(noise、random)

ThreeJS-3D教学十四:ShaderMaterial(length、fract、step) 上面这篇主要是操作 fragmentShader 片元着色器,实现对物体颜色的修改,这次咱们来看下修改 vertexShader 顶点着色器,这个其实就是位移各个顶点的位置。 接下来我们先介绍下 noise 噪声函数(Perlin Noise、Sim…...

LeetCode 2974.最小数字游戏:排序+交换奇偶位

【LetMeFly】2974.最小数字游戏:排序交换奇偶位 力扣题目链接:https://leetcode.cn/problems/minimum-number-game/ 你有一个下标从 0 开始、长度为 偶数 的整数数组 nums ,同时还有一个空数组 arr 。Alice 和 Bob 决定玩一个游戏&#xff…...

使用vllIm部署大语言模型

使用vllm部署大语言模型一般需要以下步骤: 一、准备工作 1. 系统要求 - 操作系统:常见的 Linux 发行版(如 Ubuntu、CentOS)或 Windows(通过 WSL)。 - GPU 支持:NVIDIA GPU 并安装了适当的驱动程…...

静态搜索iOS动态链接函数的调用位置

静态搜索iOS动态链接函数的调用位置 可执行文件格式mach-O,是在苹果的操作系统 macOS 和 iOS 上使用的一种二进制文件格式。 在一些iOS安全扫描中,可能存在需要获取函数具体调用位置的需求,能指导用户更精确的定位漏洞。 现在以NSLog函数为例&#xff…...

【鸿蒙学习笔记】尺寸设置・layoutWeight・对子组件进行重新布局

官方文档:尺寸设置 目录标题 layoutWeight:对子组件进行重新布局 layoutWeight:对子组件进行重新布局 设置了layoutWeight属性的子元素与兄弟元素占主轴尺寸按照权重进行分配,忽略元素本身尺寸设置。 // 引入包名 import { http…...

vue实现表单输入框数字类型校验功能

vue实现表单输入框数字类型校验功能 1. 样式代码 <el-form-item label"订单总价"><el-input size"small" v-model"form.totalPrice" placeholder"请输入订单总价 正整数或者2位数小数" input"check(form.totalPric…...

JS登录页源码 —— 可一键复制抱走

前期回顾 https://blog.csdn.net/m0_57904695/article/details/139838176?spm1001.2014.3001.5501https://blog.csdn.net/m0_57904695/article/details/139838176?spm1001.2014.3001.5501 登录页预览效果 <!DOCTYPE html> <html lang"en"><head…...

Kithara与OpenCV (一)

Kithara使用 OpenCV 库 目录 Kithara使用 OpenCV 库简介需求和支持的环境构建 OpenCV 库使用 CMake 进行配置以与 Kithara 一起工作 使用 OpenCV 库设置项目运行 OpenCV 代码图像采集和 OpenCV自动并行化限制和局限性1.系统建议2.实时限制3.不支持的功能和缺失的功能4.显示 Ope…...

什么是软件定义安全SDSec

一、软件定义安全SDSec产生的背景 软件定义安全&#xff08;Software Defined Security&#xff0c;SDSec&#xff09;的产生背景主要源于传统网络安全防护方法在面对复杂网络环境时的不适应性&#xff0c;以及软件定义网络&#xff08;SDN&#xff09;技术的发展和应用。 SD…...

【C语言】C语言可以做什么?

目录 1. 操作系统开发1.1 操作系统内核1.2 设备驱动程序1.3 系统工具和实用程序 2. 嵌入式系统2.1 微控制器编程2.2 传感器和执行器控制2.3 消费电子产品 3. 应用程序开发3.1 图形用户界面应用3.2 游戏开发3.3 多媒体处理 4. 网络编程4.1 网络协议实现4.2 服务器和客户端程序4.…...

WordPress 主题技巧:给文章页增加“谁来过”模块。

模块功能&#xff1a; 我个人目前在做一个电影类的网站&#xff0c;在开发文章页的模版时候&#xff0c;突然觉得给文章页增加一个“谁对本电影感兴趣”的功能模块可能会比较有趣&#xff0c;这个功能有点类似于‘足迹’的感觉&#xff0c;用户可以通过这个功能&#xff0c;发…...

【vue组件库搭建07】Vitest单元测试

vitest官网 vue-test-utils 我们的测试框架选择的是 Vitest 和 vue-test-utils。两者的关系为&#xff1a; Vitest 提供测试方法&#xff1a;断言、Mock 、SpyOn 等方法。vue-test-utils: 挂载和渲染组件&#xff1a; Vue Test Utils 允许您在隔离中挂载组件&#xff0c;这意…...

JSONObject和Map<String, Object>的转换

一、前言 Java开发中出参返回和入参传入更灵活的方法是使用Map<String, Object>入参或出参&#xff0c;或者使用JSONObject。 1、好处&#xff0c;参数可变&#xff0c;对接口扩展性很友好。 public ResponseData<WXModelDTO> getUserInfo(RequestBody Map<…...

C# 建造者模式(Builder Pattern)

建造者模式&#xff08;Builder Pattern&#xff09;&#xff0c;也被称为生成器模式&#xff0c;是一种对象构建模式&#xff0c;旨在将复杂对象的构建过程与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。这种模式特别适用于构建具有多个组成部分的复杂对象&am…...

初阶数据结构速成

本篇文章算是对初阶数据结构的总结&#xff0c;内容较多&#xff0c;请耐心观看 基础概念部分 顺序表 线性表&#xff08; linear list &#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构&#xff0c;常⻅的线性表&#xff1a;…...

nx上darknet的使用-目标检测-在python中的使用

1 内置的代码 在darknet中已经内置了两个py文件 darknet_video.py与darknet_images.py用法类似&#xff0c;都是改一改给的参数就行了&#xff0c;我们说一下几个关键的参数 input 要预测哪张图像weights 要使用哪个权重config_file 要使用哪个cfg文件data_file 要使用哪个da…...

Python高级(四)_内存管理

Python高级-内存管理 第四章 内存管理 1、对象池 小整数池 系统默认创建好的,等着你使用 概述:整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池,避免为整数频繁申请和销毁内存空间。Python 对小整数的定义是 [-5, 256] ,这些整数对象是提前建立好的…...

关键路径-matlab

路径上边的数目称为路径长度 图的基本知识 求最短路径&#xff08;Dijkstra算法&#xff09; 2. 待继续尝试 ①Dijkstra ②floyd_all.m 一 二 ③ LeetCode [329. 矩阵中的最长递增路径]...

JavaDS —— 单链表 与 LinkedList

顺序表和链表区别 ArrayList &#xff1a; 底层使用连续的空间&#xff0c;可以随机访问某下标的元素&#xff0c;时间复杂度为O&#xff08;1&#xff09; 但是在插入和删除操作的时候&#xff0c;需要将该位置的后序元素整体往前或者向后移动&#xff0c;时间复杂度为O&…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...

HTTPS证书一年多少钱?

HTTPS证书作为保障网站数据传输安全的重要工具&#xff0c;成为众多网站运营者的必备选择。然而&#xff0c;面对市场上种类繁多的HTTPS证书&#xff0c;其一年费用究竟是多少&#xff0c;又受哪些因素影响呢&#xff1f; 首先&#xff0c;HTTPS证书通常在PinTrust这样的专业平…...