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

掌握这7种软件设计原则,让你的代码更优雅

掌握这7种软件设计原则,让你的代码更优雅

在软件开发过程中,设计原则是非常重要的指导方针,它们可以帮助我们创建出更加清晰、可维护和可扩展的软件系统。本文将介绍7种常见的软件设计原则,并解释它们如何提升代码质量。
在这里插入图片描述

1. 单一职责原则(Single Responsibility Principle, SRP)

单一职责原则指的是一个类、方法或模块只应该有一个引起变化的原因。换句话说,每个部分都应该只负责一项功能或业务逻辑,避免功能的混杂和代码的耦合。通过遵循单一职责原则,我们可以提高代码的可读性和可维护性,使得修改和扩展更加容易。

// 违反SRP的示例:一个类做了太多事情  
class UserManager {  public void addUser(User user) {  // 添加用户的逻辑  }  public void deleteUser(User user) {  // 删除用户的逻辑  }  public void sendWelcomeEmail(User user) {  // 发送欢迎邮件的逻辑  }  
}  // 符合SRP的示例:将发送邮件的功能分离出去  
class UserManager {  public void addUser(User user) {  // 添加用户的逻辑  }  public void deleteUser(User user) {  // 删除用户的逻辑  }  
}  class EmailService {  public void sendWelcomeEmail(User user) {  // 发送欢迎邮件的逻辑  }  
}

2. 开闭原则(Open-Closed Principle, OCP)

开闭原则要求软件实体(类、模块、函数等)应该可以扩展,但不可修改。这意味着我们应该尽量通过扩展现有代码来实现新功能,而不是修改已有代码。这样做的好处是可以保持原有功能的稳定性和可靠性,同时降低引入错误的风险。实现开闭原则的一种常见方法是使用抽象和接口来定义可扩展的点。

// 定义一个接口  
interface Shape {  void draw();  
}  // 具体的实现类  
class Circle implements Shape {  @Override  public void draw() {  System.out.println("Drawing Circle");  }  
}  class Rectangle implements Shape {  @Override  public void draw() {  System.out.println("Drawing Rectangle");  }  
}  // 绘图工具类,不修改即可扩展新的形状  
class DrawingTool {  public void drawShape(Shape shape) {  shape.draw();  }  
}  // 使用示例  
public class Main {  public static void main(String[] args) {  DrawingTool tool = new DrawingTool();  Shape circle = new Circle();  tool.drawShape(circle);  Shape rectangle = new Rectangle();  tool.drawShape(rectangle);  // 如果有新的形状,只需实现Shape接口并传递给DrawingTool,无需修改DrawingTool  }  
}

3. 里氏替换原则(Liskov Substitution Principle, LSP)

里氏替换原则是由Barbara Liskov提出的,它要求子类必须能够替换其父类并且不会出现任何错误或异常。这意味着子类应该遵循父类的约定和行为,不应该引入新的约束或限制。通过遵循里氏替换原则,我们可以保证代码的兼容性和可扩展性,同时避免因为继承而引入的潜在问题。

// 基类  
class Bird {  public void fly() {  System.out.println("Flying high");  }  
}  // 派生类,遵守LSP  
class Sparrow extends Bird {  @Override  public void fly() {  System.out.println("Sparrow is flying");  }  
}  // 派生类,违反LSP(假设Ostrich不能飞)  
class Ostrich extends Bird {  @Override  public void fly() {  throw new UnsupportedOperationException("Ostrich cannot fly");  }  
}  // 使用示例  
public class Main {  public static void letItFly(Bird bird) {  bird.fly();  }  public static void main(String[] args) {  letItFly(new Sparrow()); // 正常工作  // letItFly(new Ostrich()); // 抛出异常,违反LSP  }  
}

注意:在这个例子中,Ostrich 类违反了LSP,因为它没有按照 Bird 的预期行为去飞行。在实际应用中,应该避免这种情况,例如可以通过接口来定义可飞行的鸟类。

4. 接口隔离原则(Interface Segregation Principle, ISP)

接口隔离原则要求客户端不应该依赖于它不需要的接口,或者说接口中的方法应该属于同一类职责。如果一个接口包含了多个不相关的方法,那么客户端在使用时就需要实现所有这些方法,即使它只需要其中的一部分。这会导致代码的冗余和耦合。通过将接口拆分成更小、更具体的接口,我们可以提高代码的灵活性和可重用性。

// 违反ISP的示例:接口包含不相关的方法  
interface Animal {  void eat();  void fly();  
}  // 符合ISP的示例:拆分成相关方法的接口  
interface Eatable {  void eat();  
}  interface Flyable {  void fly();  
}  // 实现类只实现它们需要的接口  
class Bird implements Eatable, Flyable {  @Override  public void eat() { /* ... */ }  @Override  public void fly() { /* ... */ }  
}  class Dog implements Eatable {  @Override  public void eat() { /* ... */ }  // Dog不需要实现fly()方法  
}

5. 依赖倒置原则(Dependency Inversion Principle, DIP)

依赖倒置原则要求高层模块不应该依赖于低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这意味着我们应该通过抽象来定义和组织模块之间的依赖关系,而不是直接依赖于具体的实现。通过这样做,我们可以降低模块之间的耦合度,提高代码的可测试性和可维护性。同时,这也使得我们可以更加灵活地替换和升级模块。

// 抽象  
interface IReader {  String read();  
}  // 具体实现  
class NewsReader implements IReader {  @Override  public String read() {  return "Reading news";  }  
}  class BookReader implements IReader {  @Override  public String read() {  return "Reading book";  }  
}  // 高层模块,依赖于抽象而不是具体实现  
class Person {  private IReader reader;  public Person(IReader reader) {  this.reader = reader;  }  public void readSomething() {  System.out.println(this.reader.read());  }  
}  // 使用示例  
public class Main {  public static void main(String[] args) {  Person personWithNews = new Person(new NewsReader());  personWithNews.readSomething(); // 输出: Reading news  Person personWithBook = new Person(new BookReader());  personWithBook.readSomething(); // 输出: Reading book  }  
}

在这个例子中,Person 类不依赖于具体的 NewsReader 或 BookReader,而是依赖于 IReader 接口。

6. 迪米特法则(Law of Demeter, LoD)或最少知道原则(Least Knowledge Principle, LKP)

迪米特法则要求一个对象应该对其他对象保持最少的了解。换句话说,每个对象都应该尽量减少与其他对象的交互和依赖,只与直接相关的对象进行通信。通过遵循迪米特法则,我们可以降低代码的复杂性和耦合度,提高代码的可读性和可维护性。同时,这也使得我们可以更加容易地替换和升级部分代码,而不会对整个系统产生太大的影响。

// 一个类应该尽量减少对其他类的了解  
class TeamLeader {  private String name;  private List<String> teamMembers = new ArrayList<>();  public TeamLeader(String name) {  this.name = name;  }  public void addTeamMember(String memberName) {  teamMembers.add(memberName);  }  public void printTeam() {  System.out.println("Team of " + name + ":");  for (String member : teamMembers) {  System.out.println("  " + member);  }  }  
}  class TeamMember {  private String name;  public TeamMember(String name) {  this.name = name;  }  public String getName() {  return name;  }  
}  // 使用示例:TeamLeader应该只知道TeamMember的名字,而不必了解TeamMember类的其他细节。  
public class Main {  public static void main(String[] args) {  TeamLeader leader = new TeamLeader("John");  TeamMember alice = new TeamMember("Alice");  leader.addTeamMember(alice.getName()); // TeamLeader只调用TeamMember的getName方法获取所需信息。  leader.printTeam(); // 输出团队信息,但TeamLeader对TeamMember的内部结构一无所知。  }  
}

在这个例子中,TeamLeader 只知道它需要 TeamMember 的名字来添加到团队列表中,而不必了解 TeamMember 类的其他任何细节。

7. 合成复用原则(Composite Reuse Principle, CRP)

合成复用原则要求尽量使用对象组合/聚合的方式来实现功能的复用,而不是使用继承关系达到软件复用的目的。因为继承会导致父类与子类之间的强耦合关系,不利于系统的扩展和维护;而使用对象组合/聚合的方式则可以将已有对象纳入新对象中,使之成为新对象的一部分,从而实现功能的复用和扩展。这样做的好处是可以保持代码的灵活性和可扩展性,同时降低引入错误的风险。

// 通过组合/聚合来实现复用,而不是继承  
class Engine {  public void run() {  System.out.println("Engine is running");  }  
}  class Car {  private Engine engine;  public Car(Engine engine) {  this.engine = engine;  }  public void start() {  engine.run();  }  
}  // 使用示例  
public class Main {  public static void main(String[] args) {  Engine dieselEngine = new Engine() {  @Override  public void run() {  System.out.println("Diesel engine is running");  }  };  Car dieselCar = new Car(dieselEngine);  dieselCar.start(); // 输出: Diesel engine is running  // 可以很容易地替换成其他类型的引擎,而不需要修改Car类  }  
}

在这个例子中,Car 类通过组合一个 Engine 对象来实现其功能,而不是通过继承一个包含 run 方法的基类。这样做的好处是 Car 可以灵活地与不同类型的 Engine 一起使用,提高了代码的复用性和可扩展性。注意,这里为了简单起见,我直接在 main 方法中创建了一个匿名内部类来模拟不同类型的引擎;在实际应用中,你可能会创建多个实现了相同接口的具体引擎类。

总结:

以上介绍了7种常见的软件设计原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则、迪米特法则和合成复用原则。这些原则在软件开发过程中起着重要的指导作用,它们可以帮助我们创建出更加清晰、可维护和可扩展的软件系统。在实际开发中,我们应该根据项目的需求和特点选择合适的设计原则来指导我们的编码工作。

相关文章:

掌握这7种软件设计原则,让你的代码更优雅

掌握这7种软件设计原则&#xff0c;让你的代码更优雅 在软件开发过程中&#xff0c;设计原则是非常重要的指导方针&#xff0c;它们可以帮助我们创建出更加清晰、可维护和可扩展的软件系统。本文将介绍7种常见的软件设计原则&#xff0c;并解释它们如何提升代码质量。 1. 单…...

Flutter自定义tabbar任意样式

场景描述 最近在使用遇到几组需要自定义的tabbar或者类似组件&#xff0c;在百度查询资料中通常&#xff0c;需要自定义 TabIndicator extends Decoration 比如上图中的带圆角的指示器这样实现 就很麻烦&#xff0c; 搜出来的相关也是在此之处上自己画&#xff0c;主要再遇…...

Java设计模式【策略模式】

一、前言 1.1 背景 针对某种业务可能存在多种实现方式&#xff0c;传统方式是通过传统if…else…或者switch代码判断&#xff1b; 弊端&#xff1a; 代码可读性差扩展性差难以维护 1.2 简介 策略模式是一种行为型模式&#xff0c;它将对象和行为分开&#xff0c;将行为定…...

(13)Hive调优——动态分区导致的小文件问题

前言 动态分区指的是&#xff1a;分区的字段值是基于查询结果自动推断出来的&#xff0c;核心语法就是insertselect。 具体内容指路文章&#xff1a; https://blog.csdn.net/SHWAITME/article/details/136111924?spm1001.2014.3001.5501文章浏览阅读483次&#xff0c;点赞15次…...

【linux】使用g++调试内存泄露:AddressSanitizer

1、简介 AddressSanitizer(又名 ASan)是 C/C++ 的内存错误检测器。它可以用来检测: 释放后使用(悬空指针) 堆缓冲区溢出 堆栈缓冲区溢出 全局缓冲区溢出 在作用域之后使用 初始化顺序错误 内存泄漏这个工具非常快,只将被检测的程序速度减慢约2倍,而Valgrind将会是程序…...

第三百五十七回

文章目录 1. 概念介绍2. 使用方法2.1 List2.2 Map2.3 Set 3. 示例代码4. 内容总结 我们在上一章回中介绍了"convert包"相关的内容&#xff0c;本章回中将介绍collection.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的内容是col…...

新版Java面试专题视频教程——框架篇

新版Java面试专题视频教程——框架篇 框架篇 01-框架篇介绍02-Spring-单例bean是线程安全的吗03-Spring-AOP相关面试题04-Spring-事务失效的场景05-Spring-bean的生命周期5.1 BeanDefinition 06-Spring-bean的循环依赖(循环引用)6.1 一般对象的循环依…...

网络爬虫实战 | 上传以及下载处理后的文件

详细代码在文尾 以实现爬虫一个简单的(SimFIR (doctrp.top))网址为例,需要遵循几个步骤: 1. 分析网页结构 首先,需要分析该网页的结构,了解图片是如何存储和组织的。这通常涉及查看网页的HTML源代码,可能还包括CSS和JavaScript文件。检查图片URL的模式,看看是否有规律…...

Linux--shell编程中有关while循环的详细内容

文章关于while循环的内容目录 一、while循环 ​​​​​​​​​​​​​​二、无限循环 ​​​​​​​​​​​​​​三、case语句 ​​​​​​​四、跳出循环 ​​​​​​​​​​​​​​五、break ​​​​​​​六、continue​​​​​​​ ​​​​​​​一、w…...

回归测试与重新测试

软件开发是一个充满挑战的旅程&#xff0c;在这条道路上始终伴随着错误和不确定性的挑战。然而&#xff0c;真正将卓越软件与其他软件区分开来的是管理和解决这些挑战的效率&#xff0c;这就是结构良好的测试计划变得至关重要的地方&#xff0c;该计划的核心在于两个基本实践&a…...

java 版本企业招标投标管理系统源码+多个行业+tbms+及时准确+全程电子化

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…...

详解动态内存管理!

目录 ​编辑 1.为什么要用动态内存分配 2.malloc和free 2.1 malloc 2.2 free 3.calloc和realloc 3.1 calloc 3.2 realloc 4.常见的动态内存的错误 4.1 对NULL的解引用操作 4.2 对动态内存开辟空间的越界访问 4.3 对非动态内存开辟空间用free释放 4.4 使用free释放动…...

iocp简单例子

下方代码中&#xff0c;没有写注释的地方&#xff0c;说明与icop网络无关也就是它们都不重要&#xff0c;重要的位置全部都有注释&#xff0c;复制下方代码就可以运行看效果 iocp带网络的例子&#xff1a; 客户端&#xff1a; 客户端只有一个main&#xff0c;只有socket相关函…...

HAL STM32 HW I2C DMA + SSD1306/SH1106驱动示例

HAL STM32 HW I2C DMA SSD1306/SH1106驱动示例 &#x1f4cd;硬件I2C DMA驱动参考&#xff1a;https://blog.csdn.net/weixin_45065888/article/details/118225993 &#x1f516;本工程基于STM32F103VCT6&#xff0c;驱动程序独立&#xff0c;可以移植到任意STM32型号上使用。…...

grafana配置钉钉告警模版(一)

1、配置钉钉告警模版 创建钉钉告警模版&#xff0c;然后在创建钉钉告警时调用模版。 定义发送内容具体代码 my_text_alert_list 是模版名称后面再配置钉钉告警时需要调用。 {{/* 定义消息体片段 */}} {{ define "my_text_alert_list" }}{{ range . }}告警名称&…...

佳能2580的下载手册

凡是和电子产品有关的产品其内部都开始不断地进行内卷&#xff0c;在不断地内卷背后&#xff0c;意味着科技更新和换代&#xff0c;自己也入手了一台佳能2580的打印机&#xff0c;一台相对比较老式的打印机&#xff0c;以此不断地自己想要进行打印的需要。 下载的基础步骤&…...

YOLO-World:实时开放词汇目标检测

paper&#xff1a;https://arxiv.org/pdf/2401.17270.pdf Github&#xff1a;GitHub - AILab-CVC/YOLO-World: Real-Time Open-Vocabulary Object Detection online demo&#xff1a;https://huggingface.co/spaces/stevengrove/YOLO-World 目录 0. 摘要 1. 引言 2. 相关工…...

Unity中关于群组的一些组件

前言 在游戏开发环境中&#xff0c;UI组件是构建玩家交互界面的基础。以下是一些常见UI组件的详细解释和它们适用的场景&#xff0c;方便我们更好地理解和使用这些工具。 1. Graphic Raycaster Graphic Raycaster组件是游戏UI交互的核心。在Unity等游戏引擎中&#xff0c;当玩…...

面向对象详解,面向对象的三大特征:封装、继承、多态

文章目录 一、面向对象与面向过程1、什么是面向过程&#xff1f;2、什么是面向对象&#xff1f; 二、类与对象1. 初识对象2. 类的成员方法2.1 类的定义和使用2.2 成员方法 3. 类和对象4. 魔法方法1. _ _ inint _ _ 构造方法2. _ _ str _ _ 字符串方法3. _ _ lt _ _ 小于符号比较…...

【阿里云服务器的一些使用坑】都是无知的泪水呀

发生了什么&#xff1f; 我想学习一下关于Java的MySQL、Nginx 相关的知识。然后就用首次优惠注册的阿里云&#xff0c;都没有搞清楚实例&#xff0c;镜像&#xff0c;带宽&#xff0c;磁盘。然后。因为一不小心——我想去换一个Ubuntu的镜像而不是CentOS。就把实例给释放啊。之…...

快速验证c盘清理方案,用快马平台十分钟搭建原型工具

最近电脑C盘总是爆满&#xff0c;系统频繁弹窗提示空间不足&#xff0c;严重影响工作效率。作为一个非专业开发者&#xff0c;我尝试用InsCode(快马)平台快速搭建了一个C盘清理工具原型&#xff0c;整个过程比想象中简单许多。这里分享我的实现思路和具体操作步骤&#xff0c;或…...

ESP8266 EEPROM实战:手把手教你存WiFi密码,断电重启也不怕

ESP8266 EEPROM实战&#xff1a;构建可靠的WiFi凭证存储系统 每次重启ESP8266设备都要重新输入WiFi密码&#xff1f;这种重复劳动早就该被技术淘汰了。想象一下&#xff0c;你的智能家居设备在断电恢复后能自动重新连接网络&#xff0c;工业传感器在意外重启后依然保持通信——…...

如何用Obsidian构建你的个人知识管理系统:终极完整指南

如何用Obsidian构建你的个人知识管理系统&#xff1a;终极完整指南 【免费下载链接】kepano-obsidian My personal Obsidian vault template. A bottom-up approach to note-taking and organizing things I am interested in. 项目地址: https://gitcode.com/gh_mirrors/ke/…...

一文吃透Redis集群:架构、原理、搭建与实战优化

在分布式系统中&#xff0c;Redis作为高性能的键值存储中间件&#xff0c;单机部署早已无法满足高并发、大容量的业务需求——当数据量突破单机内存上限、QPS达到万级以上&#xff0c;单机Redis的单点故障、性能瓶颈会直接影响业务稳定性。此时&#xff0c;Redis集群&#xff0…...

Joy-Con Toolkit开源工具:Switch手柄深度定制与性能优化方案

Joy-Con Toolkit开源工具&#xff1a;Switch手柄深度定制与性能优化方案 【免费下载链接】jc_toolkit Joy-Con Toolkit 项目地址: https://gitcode.com/gh_mirrors/jc/jc_toolkit Joy-Con Toolkit是一款面向任天堂Switch玩家的开源手柄管理工具&#xff0c;提供专业级传…...

SAP MM进阶:解密DESADV IDoc如何打通公司间STO的‘任督二脉’

SAP MM进阶&#xff1a;DESADV IDoc在公司间STO流程中的核心作用解析 在集团化企业的供应链管理中&#xff0c;公司间库存转储订单&#xff08;STO&#xff09;的高效执行往往决定着整个供应链的响应速度。当货物从发货方仓库运出时&#xff0c;如何确保收货方能实时获取发货信…...

Koikatu HF Patch完整安装指南:5步轻松解锁游戏全部潜力

Koikatu HF Patch完整安装指南&#xff1a;5步轻松解锁游戏全部潜力 【免费下载链接】KK-HF_Patch Automatically translate, uncensor and update Koikatu! and Koikatsu Party! 项目地址: https://gitcode.com/gh_mirrors/kk/KK-HF_Patch 还在为Koikatu游戏体验不完整…...

PostGIS中ST_Area计算面积时单位转换的实用技巧

1. 为什么ST_Area在WGS84坐标系下计算结果不对劲&#xff1f; 第一次用PostGIS的ST_Area函数计算地理围栏面积时&#xff0c;我盯着屏幕上那个小得离谱的数字愣了半天——0.000002&#xff1f;这还没我家卫生间大&#xff01;后来才发现&#xff0c;原来90%的新手都会在这个坑里…...

【MobaXterm进阶】SSH连接稳定性优化:Keepalive与超时设置详解

1. 为什么SSH连接会频繁断开&#xff1f; 很多朋友在用MobaXterm远程连接服务器时都遇到过这样的困扰&#xff1a;明明连接得好好的&#xff0c;过一会儿就莫名其妙断开了。特别是当你正在执行一个耗时较长的任务时&#xff0c;突然中断简直让人抓狂。这种情况在家庭版用户中尤…...

从零到一实战:基于快马AI生成企业级RESTful API服务器代码

最近在做一个图书管理系统的项目&#xff0c;需要搭建一个完整的RESTful API服务器。作为一个全栈开发者&#xff0c;我决定尝试用InsCode(快马)平台来快速生成服务器代码&#xff0c;没想到效果出奇地好。下面分享下我的实战经验。 项目需求分析 首先明确需要实现的功能&#…...