C#(14)七大原则

前言

其实在面向对象设计里,程序猿们互相约定好一些原则,即七大原则。

面向对象的七大原则是一组指导软件设计的原则,旨在帮助开发人员实现松耦合、可维护和可扩展的软件系统。这些原则的设计过程和发展历史可以追溯到20世纪80年代。

  1. 单一职责原则(Single Responsibility Principle):这个原则最早由罗伯特·C·马丁(Robert C. Martin)提出,并在他的《敏捷软件开发:原则、模式和实践》一书中详细阐述。该原则指出,一个类应该只有一个引起变化的原因,即一个类应该只负责一项职责。这样可以实现类的高内聚性和低耦合性。

  2. 开放关闭闭原则(Open-Closed Principle):开放封闭原则由伯特兰·梅耶(Bertrand Meyer)提出,他在他的《面向对象软件构造》一书中详细阐述了该原则。该原则指出,一个软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需求改变时,应该通过扩展现有实体而不是修改已有代码。

  3. 里氏替换原则(Liskov Substitution Principle):里氏替换原则由芭芭拉·利斯科夫(Barbara Liskov)提出,并在她的论文《数据抽象和层次类型》中详细阐述。该原则指出,子类对象应该能够替换所有使用基类对象的地方,而不会产生错误或违反系统的行为。这可以保证继承关系的正确性。

  4. 依赖倒置原则(Dependency Inversion Principle):依赖倒置原则由罗伯特·C·马丁提出,并在他的《敏捷软件开发:原则、模式和实践》一书中详细阐述。该原则指出,高层模块不应该依赖于低层模块,它们应该依赖于抽象。这样可以实现模块之间的松耦合。

  5. 接口隔离原则(Interface Segregation Principle):接口隔离原则由罗伯特·C·马丁提出,并在他的《敏捷软件开发:原则、模式和实践》一书中详细阐述。该原则指出,客户端不应该依赖于它不需要的接口。一个类只应该依赖于它所使用的接口,避免了不必要的依赖。

  6. 迪米特法则(Law of Demeter)(也叫最少知识原则):迪米特法则由伊恩·霍洛维茨(Ian Holland)和巴斯卡尔·勒格兰(Pascal Leroux)提出,并在他们的论文《迪米特法则对面向对象设计的影响》中详细阐述。该原则指出,一个对象应该对其他对象保持最少的了解,只与直接的朋友交流。这样可以减少对象之间的耦合。

  7. 合成复用原则(Composite Reuse Principle):合成复用原则由伊恩·霍洛维茨和巴斯卡尔·勒格兰提出,并在他们的论文《迪米特法则对面向对象设计的影响》中详细阐述。该原则指出,尽量使用对象组合,而不是继承来实现代码的复用。这样可以使系统更加灵活和可扩展。

随着面向对象编程的兴起和软件开发的需求不断演变,它们得到了广泛的应用和发展。这些原则的目标是提高软件系统的可维护性、可扩展性和可重用性,使软件的开发过程更加灵活和高效,而今天我们也将详细讲讲七大原则,希望能在日后的编程对你有所帮助。

简述

  • 单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个变化的原因,即每个类应该只负责一项功能。

  • 开放-关闭原则(Open/Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,可以通过扩展现有代码而不是修改它来实现新功能。

  • 里氏替换原则(Liskov Substitution Principle,LSP):子类对象应该能够替换父类对象而不影响程序的正确性,确保子类的行为符合父类的预期。

  • 接口隔离原则(Interface Segregation Principle,ISP):不应强迫客户端依赖于它们不使用的接口。应该将大的接口拆分成多个小接口,以便于实现和使用。

  • 依赖反转原则(Dependency Inversion Principle,DIP):高层模块不应依赖于低层模块,二者都应依赖于抽象(接口或抽象类),而不应依赖于具体实现。

  • 合成复用原则(Composite Reuse Principle,CRP):尽量使用对象的组合而不是继承来实现代码复用。组合关系比继承关系更灵活。

  • 最少知识原则(Least Knowledge Principle,LKP):一个对象应该对其他对象有最少的了解。减少对象之间的耦合,通过公共接口进行交互。

可能你单看文字很多都还看不懂,有些东西甚至需要你学习了之后的东西再回来看,如果你是顺序查看博主的博文的话,建议只看里氏替换原则。

单一职责原则 

基本概念

强调一个类应该只有一个单一的责任,即一个类应该仅仅负责一个功能或任务。

重点

  • 单一性:每个类保持单一功能,简化类的接口。
  • 职责划分:明确界定类的职责,避免交叉影响。
  • 易维护性:变更一个责任时只需修改相关类,降低了风险。

作用

  • 提高可读性和可理解性:清晰的职责使得代码更易于阅读和理解。
  • 提升可维护性:减少了因改动引入的bug,因为每个类变更都与其单一的功能相关。
  • 增强可重用性:聚焦于单一功能的类可以更容易地被重用于其他项目中。

示例

这个示例是其实也是大家在日后unity的设计中也经常使用的模式,当然,gamemanager用来具体干什么,就要视情况而定了。

using System;
using System.Collections.Generic;// 游戏管理类,负责管理游戏逻辑
class GameManager
{private Player player;private List<Enemy> enemies;public GameManager(){player = new Player();enemies = new List<Enemy>();InitializeEnemies();}// 初始化敌人private void InitializeEnemies(){enemies.Add(new Enemy("Enemy 1"));enemies.Add(new Enemy("Enemy 2"));enemies.Add(new Enemy("Enemy 3"));}// 游戏主循环public void Run(){while (true){player.Update();foreach (Enemy enemy in enemies){enemy.Update();}if (player.IsCollidingWithEnemy(enemies)){Console.WriteLine("Player collided with an enemy");break;}}}
}// 玩家类,负责玩家相关逻辑
class Player
{public void Update(){Console.WriteLine("Player is updating");}public bool IsCollidingWithEnemy(List<Enemy> enemies){// 检测玩家是否与敌人发生碰撞foreach (Enemy enemy in enemies){if (enemy.Position == this.Position){return true;}}return false;}// 玩家的其他属性和方法...
}// 敌人类,负责敌人相关逻辑
class Enemy
{public string Name { get; private set; }public int Position { get; private set; }public Enemy(string name){Name = name;Position = 0;}public void Update(){Console.WriteLine($"{Name} is updating");Position++;}// 敌人的其他属性和方法...
}class Program
{static void Main(string[] args){GameManager game = new GameManager();game.Run();}
}

开放-关闭原则

基本概念

开放-关闭原则是对象设计中的一种原则,其核心思想是“软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。

重点

  • 开放性:允许在不改变现有代码的情况下添加新功能。
  • 封闭性:现有的代码在功能上不会被修改,应该能安全地被使用。
  • 通过继承和接口等机制支持灵活的扩展。

作用

  • 增强软件的可维护性和可扩展性。
  • 减少代码修改带来的风险,降低意外引入bug的可能性。
  • 促进模块化设计,使得系统可以方便地进行部件替换或升级。

示例

Main方法中,我们创建了一个包含一个战士和一个法师角色的游戏场景对象,并调用了RunGame()方法。由于Character类是开放的,我们可以随时添加新的角色类而不需要修改GameScene类的代码,同时GameScene类的代码是关闭的,不需要对新的角色类进行修改,这就是所谓的开放关闭原则。

using System;// 游戏角色基类
abstract class Character
{public abstract void Attack();//可以在这里添加方法
}// 战士角色
class Warrior : Character
{public override void Attack(){Console.WriteLine("战士发起了一次普通攻击!");}
}// 法师角色,你还可以写一个牧师角色(相当于开放的)
class Mage : Character
{public override void Attack(){Console.WriteLine("法师施放了一次火球术!");}
}// 游戏场景类(相当于关闭了,不用管里面的)
class GameScene
{private Character[] characters;public GameScene(Character[] characters){this.characters = characters;}public void RunGame(){foreach (Character character in characters){character.Attack();}}
}class Program
{static void Main(string[] args){Character[] characters = { new Warrior(), new Mage() };GameScene gameScene = new GameScene(characters);gameScene.RunGame();}
}

里氏替换原则

基本概念

任何父类出现的地方,子类都可以替代

重点

语法表现——父类容器装子类对象,因为子类对象包含了父类的所有内容

作用

  • 方便对象存储和管理

示例

简单写一个,我就不多解释了,就是简单的父类装子类。

using System;public class Shape
{public virtual double CalculateArea(){return 0;}
}public class Rectangle : Shape
{public double Width { get; set; }public double Height { get; set; }public override double CalculateArea(){return Width * Height;}
}public class Square : Shape
{public double SideLength { get; set; }public override double CalculateArea(){return SideLength * SideLength;}
}public class Program
{public static void Main(string[] args){Shape shape1 = new Rectangle { Width = 5, Height = 10 };Shape shape2 = new Square { SideLength = 5 };Console.WriteLine("Rectangle Area: " + shape1.CalculateArea());Console.WriteLine("Square Area: " + shape2.CalculateArea());}
}

接口隔离原则

基本概念

核心思想是“客户端不应该被迫依赖于它不使用的接口。” 简而言之,就是每个接口应该只包含客户端所需的方法,避免将多个不相关的方法聚合在一个接口中。

重点

  • 细化接口:将大接口分拆为多个小接口,使得实现这些接口的类更为专注。
  • 降低耦合度:使得类与接口的依赖关系更为精确,减小了系统之间的耦合,增强了灵活性。
  • 提高可维护性:降低了不必要的方法对实现类的影响,修改接口时影响范围更小。

作用

  • 增强系统的模块化,易于理解和维护。
  • 提高代码的复用性,使不同的类能够更灵活地选择需要实现的接口。
  • 降低修改某个接口时,导致其他代码破坏的风险。

示例

直接看例子,这个规范光靠说的话也很简单,就是不能把太多功能耦合到一个东西身上,打个比方,有些gamejam的顶级程序猿能把武器,道具这些全写到人物里面去,你可以从自己角度简单评价一下这个代码维护起来有多么逆天。

using System;// 定义不同接口:播放音频和播放视频分开,当然实际开发不一定这样分,但是你要知道为什么要这样规定
interface IPlayer
{void Play();
}interface IRecord
{void Record();
}// 实现接口
class VideoPlayer : IPlayer
{public void Play(){Console.WriteLine("开始播放视频");}
}class AudioPlayer : IPlayer, IRecord
{public void Play(){Console.WriteLine("开始播放音频");}public void Record(){Console.WriteLine("开始录音");}
}class Game
{private IPlayer player;public Game(IPlayer player){this.player = player;}public void Start(){player.Play();}
}class Program
{static void Main(string[] args){IPlayer videoPlayer = new VideoPlayer();IPlayer audioPlayer = new AudioPlayer();Game videoGame = new Game(videoPlayer);videoGame.Start(); // 输出:开始播放视频Game audioGame = new Game(audioPlayer);audioGame.Start(); // 输出:开始播放音频}
}

依赖反转原则

基本概念

这个的主要思想是“高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖于细节,细节应该依赖于抽象。”说人话,依赖于抽象(接口或抽象类)而不是具体实现,这样可以减少模块之间的耦合。

重点

  • 高层模块:指的是完成业务逻辑等高级功能的部分。
  • 低层模块:指的是具体的实现细节,例如数据访问或硬件控制。
  • 抽象:通常是一个接口或抽象类,用于定义高层模块和低层模块之间的交互。

作用

  • 提高系统的灵活性和可扩展性,方便替换实现。
  • 减少模块之间的耦合,使得更改低层实现时不影响高层逻辑。
  • 提高代码的可测试性,通过依赖注入等方式简化单元测试。

示例 

示例中,我们定义了一个接口 IWeapon,以及两个实现类 Sword 和 Bow。然后,在 Player 类中通过构造函数注入 IWeapon 接口的实例,以实现依赖反转原则。这样,我们可以根据需要选择不同的武器,而不需要修改 Player 类的代码。

在 Game 类的 Main 方法中,我们创建了一个 Player 实例,分别使用剑和弓箭进行攻击。这样,我们可以灵活地为角色选择不同的武器,而不需要修改 Game 类的代码。

using System;// 定义接口
public interface IWeapon
{void Attack();
}// 定义实现类
public class Sword : IWeapon
{public void Attack(){Console.WriteLine("使用剑攻击");}
}public class Bow : IWeapon
{public void Attack(){Console.WriteLine("使用弓箭攻击");}
}// 定义高层模块
public class Player
{private IWeapon weapon;// 通过构造函数注入依赖public Player(IWeapon weapon){this.weapon = weapon;}//避免了你在这里面写一堆道具和武器的方法public void Attack(){weapon.Attack();}
}// 示例程序
public class Game
{public static void Main(string[] args){IWeapon sword = new Sword();Player player1 = new Player(sword);player1.Attack();IWeapon bow = new Bow();Player player2 = new Player(bow);player2.Attack();}
}

合用复用原则

基本概念

大概意思是“你不会需要它”。这个原则强调在软件开发中,开发者不应该添加过多的功能或代码,只应当实现当前需求所需的功能,避免为了未来可能需要的功能而过度设计。

重点

  • 避免过度设计:只开发当前需求所需的功能,避免考虑和实现未来可能不必要的功能。
  • 简化代码:减少无谓的复杂性,让代码保持简洁和清晰。
  • 提高维护性:随着代码变得复杂,维护的成本会增加,原则帮助保持代码的可维护性。

作用

  • 降低项目的复杂度:代码更加简单,易于理解和维护。
  • 提高开发效率:避免不必要的功能开发,从而节省时间和资源。
  • 降低出错概率:减少不必要的逻辑和功能可以降低 bug 的数量,提高系统稳定性。

示例 

没有示例,想要告诉你的更多是你要记住,不要画蛇添足

最少知识原则

基本概念

最少知识原则是面向对象设计中的一项原则,强调一个对象应当对其他对象有尽可能少的了解。说人话,一个对象应该只与直接的朋友(合作对象)进行交互,而不应该去了解其他对象之间的复杂关系。

重点

  • 直接交互:对象只应与直接相关的对象进行通信,避免“连锁调用”。
  • 封装性:通过减少对象间的知识,增强封装性,使对象能独立变化。
  • 降低耦合:减少模块之间的依赖,有助于系统的维护和扩展。

作用

  • 提高可维护性:系统的修改和维护更容易,因为对象之间的依赖关系被减少。
  • 增强可读性:代码更容易理解,减少了对象之间复杂的交互模式。
  • 促进独立性:使得各个模块之间能独立发展,减少了相互影响的风险。

示例 

错误示范:假设我们有一个系统,其中 Order 类依赖于 Customer 和 Address 类

public class Address  
{  public string Street { get; set; }  public string City { get; set; }  
}  public class Customer  
{  public Address Address { get; set; }  
}  public class Order  
{  public Customer Customer { get; set; }  public void PrintShippingAddress()  {  // 连锁调用,不符合最少知识原则  Console.WriteLine($"Shipping to: {Customer.Address.Street}, {Customer.Address.City}");  }  
}

改进后:

public class Address  
{  public string Street { get; set; }  public string City { get; set; }  public string GetFullAddress()  {  return $"{Street}, {City}";  }  
}  public class Customer  
{  public Address Address { get; set; }  public string GetShippingAddress()  {  return Address.GetFullAddress();  }  
}  public class Order  
{  public Customer Customer { get; set; }  public void PrintShippingAddress()  {  // 只与 Customer 交互,符合最少知识原则  Console.WriteLine($"Shipping to: {Customer.GetShippingAddress()}");  }  
}

我想这个示例能让你明白这是什么个情况

总结

七大原则本身其实是大家不断探索后发现的约定,其实你可以不这样写程序,当然你在遇到长期项目的时候必然会遇到很多问题,规范的代码有主意你更好地进行长线开发。

可能博主对着七大原则的理解也有一些误区,欢迎批评指正。

还是那句话,学习路上,脚踏实地。

请期待我的下一篇博客!

我来自佑梦游戏开发,感谢你的关注和收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/479432.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode的markdown扩展问题

使用vscode编辑markdown文本时&#xff0c;我是用的是Office Viewer(Markdown Editor)这个插件 今天突然发现不能用了&#xff0c;点击切换编辑视图按钮时会弹出报错信息&#xff1a; command office.markdown.switch not found 在网上找了很久发现没有有关这个插件的文章………

从零开始学 Maven:简化 Java 项目的构建与管理

一、关于Maven 1.1 简介 Maven 是一个由 Apache 软件基金会开发的项目管理和构建自动化工具。它主要用在 Java 项目中&#xff0c;但也可以用于其他类型的项目。Maven 的设计目标是提供一种更加简单、一致的方法来构建和管理项目&#xff0c;它通过使用一个标准的目录布局和一…

去哪儿大数据面试题及参考答案

Hadoop 工作原理是什么&#xff1f; Hadoop 是一个开源的分布式计算框架&#xff0c;主要由 HDFS&#xff08;Hadoop 分布式文件系统&#xff09;和 MapReduce 计算模型两部分组成 。 HDFS 工作原理 HDFS 采用主从架构&#xff0c;有一个 NameNode 和多个 DataNode。NameNode 负…

守护进程

目录 守护进程 前台进程 后台进程 session&#xff08;进程会话&#xff09; 前台任务和后台任务比较好 本质 绘画和终端都关掉了&#xff0c;那些任务仍然在 bash也退了&#xff0c;然后就托孤了 ​编辑 守护进程化---不想受到任何用户登陆和注销的影响​编辑 如何…

element ui select绑定的值是对象的属性时,显示异常.

需要声明 value-key"value". el-select v-model"value" clearable placeholder"Select" value-key"value" style"width: 240px"><!-- <el-option v-for"item in options" :key"item.value" :…

SAAS美容美发系统架构解析

随着技术的不断发展&#xff0c;SAAS&#xff08;Software as a Service&#xff0c;软件即服务&#xff09;模式在各个行业的应用逐渐深化&#xff0c;美容美发行业也不例外。传统的美容美发店面通常依赖纸质记录、手动操作和复杂的管理流程&#xff0c;而随着SAAS平台的出现&…

[代码随想录Day24打卡] 93.复原IP地址 78.子集 90.子集II

93.复原IP地址 一个合法的IP地址是什么样的&#xff1a; 有3个’.分割得到4个数&#xff0c;每个数第一个数不能是0&#xff0c;不能含有非法字符&#xff0c;不能大于255。 这个是否属于合法IP相当于一个分割问题&#xff0c;把一串字符串分割成4部分&#xff0c;分别判断每…

Java学习笔记--继承方法的重写介绍,重写方法的注意事项,方法重写的使用场景,super和this

目录 一&#xff0c;方法的重写 二&#xff0c;重写方法的注意事项 三&#xff0c;方法重写的使用场景 四&#xff0c;super和this 1.继承中构造方法的特点 2.super和this的具体使用 super的具体使用 this的具体使用 一&#xff0c;方法的重写 1.概述:子类中有一个和父类…

gRPC 双向流(Bidirectional Streaming RPC)的使用方法

gRPC 是一个支持多种语言的高性能 RPC 框架&#xff0c;拥有丰富的 API 来简化服务端和客户端的开发过程。gRPC 支持四种 RPC 类型&#xff1a;Unary RPC、Server Streaming RPC、Client Streaming RPC 和 Bidirectional Streaming RPC。下面是双向流 API 的使用方法。 双向流…

npm install -g@vue/cli报错解决:npm error code ENOENT npm error syscall open

这里写目录标题 报错信息1解决方案 报错信息2解决方案 报错信息1 使用npm install -gvue/cli时&#xff0c;发生报错&#xff0c;报错图片如下&#xff1a; 根据报错信息可以知道&#xff0c;缺少package.json文件。 解决方案 缺什么补什么&#xff0c;这里我们使用命令npm…

【ComfyUI】前景分割ComfyUI-BiRefNet-Hugo (无法选定分割的主体,背景鉴别由模型数据,也叫二分分割,显著性分割)

源码&#xff1a;https://github.com/ZhengPeng7/BiRefNet comfyui插件&#xff1a;https://github.com/MoonHugo/ComfyUI-BiRefNet-Hugo 模型下载地址&#xff1a;https://huggingface.co/ZhengPeng7/BiRefNet 工作流以及相关资源下载&#xff1a;https://pan.baidu.com/s/1-U…

大数据技术之Spark :我快呀~

在 MapReduce 为海量数据的计算服务多年后&#xff0c;随着时代的发展和 Spark 等新技术的出现&#xff0c;它的劣势也慢慢的凸显出来了&#xff1a; 执行速度慢。编程复杂度过高。 先看第一点 2000 年代诞生的 MapReduce &#xff0c;因为计算资源有限&#xff0c;所以 Map…

新160个crackme - 105-royalaccezzcrackme

运行分析 需破解Name和Serial&#xff0c;点击OK没反应 PE分析 ASM程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida找到关键字符串 进行静态分析&#xff0c;逻辑如下&#xff1a;1、Name长度大于4&#xff0c;小于212、fun_1返回值为1 对func_1进行动态调试分…

【RISC-V CPU 专栏 -- 香山处理器介绍】

文章目录 RISC-V 香山处理器介绍雁栖湖处理器南湖处理器RISC-V 香山处理器介绍 相信很多小伙伴对于“香山”都不陌生,它是一款开源RISC-V处理器核,香山的每一代架构,都是采用了湖的名字,第一代架构被命名为雁栖湖,第二代架构则叫做 “南湖”。 “雁栖湖”这款处理器的 R…

远程视频验证如何改变商业安全

如今&#xff0c;商业企业面临着无数的安全挑战。尽管企业的形态和规模各不相同——从餐厅、店面和办公楼到工业地产和购物中心——但诸如入室盗窃、盗窃、破坏和人身攻击等威胁让安全主管时刻保持警惕。 虽然传统的监控摄像头网络帮助组织扩大了其态势感知能力&#xff0c;但…

【TQ2440】02 串口连接进入u-boot

需要收到的板子已经烧写好系统或u-boot&#xff0c;看开机液晶屏底下的四个LED灯有没有亮黄绿色&#xff0c;没有就是还没烧写u-boot&#xff0c;需要先使用Jlink烧写u-boot 进入 uboot 的下载模式&#xff0c;如果从 Nor Flash 启动默认的就是进入 uboot 的下载模式&#xff…

QCommandLinkButton控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…

【Vue】Ego商城项目跟做

技术栈 Vue全家桶&#xff1a;Vue VueRouter Vuex Axios ElementUI 依赖安装 网络请求&#xff1a;npm install --save axios --no-fund Element&#xff1a;vue add element 后端相关依赖&#xff1a;npm install --save express cors mysql --no-fund token&#xff1a;np…

python简单算法

冒泡 def boll(lis):i 0while i<len(lis)-1:j 0while j<len(lis)-1-i:if lis[j] > lis[j1]:lis[j],lis[j 1] lis[j1],lis[j]j1i1选择排序 def selct1(lit):i 0while i<len(lit)-1:j i1min1 iwhile j < len(lit):if lit[j] < lit[min1]:min1 jj 1li…

2024年第15届蓝桥杯C/C++组蓝桥杯JAVA实现

目录 第一题握手&#xff0c;这个直接从49累加到7即可&#xff0c;没啥难度&#xff0c;后面7个不握手就好了&#xff0c;没啥讲的&#xff0c;(然后第二个题填空好难&#xff0c;嘻嘻不会&#xff09; 第三题.好数​编辑 第四题0R格式 宝石组合 数字接龙 最后一题:拔河 第…