13-C#.Net-设计模式六大原则-学习笔记

C#知识库:你的“知识管家”有多懂你? 🔥关注墨瑾轩,带你探索编程的奥秘!🚀🔥超萌技术攻略,轻松晋级编程高手🚀🔥技术宝库已备好,就等你来挖掘🚀🔥订阅墨瑾轩,智趣学习不孤单🚀🔥即刻启航,编程之旅更有趣🚀C#知识库不仅是开发者的技术指南,更是你的“智能助手”——它能像女朋友一样贴心,却又比她更理性、更可靠。 阅读详情

六大原则是写出高质量、可维护代码的基础,记住一句话:对修改关闭,对扩展开放是核心思想,其余五条都是在不同角度强化这个目标。

一、SRP — 单一职责原则

Single Responsibility Principle

核心思想

一个类(或方法)只负责一件事,只有一个引起它变化的原因。

通俗理解

厨师只管做菜,服务员只管上菜,收银员只管收钱。如果一个人全干,哪个环节出问题都要改这个人。

代码中的问题演示

// 反例:Animal 类的 Breath() 方法承担了所有动物的呼吸逻辑
public void Breath()
{
    if (_Name.Equals("鸡")) Console.WriteLine("呼吸空气");
    else if (_Name.Equals("鱼")) Console.WriteLine("呼吸水");
    // 每新增一种动物就要改这个方法 —— 违反SRP
}

正确做法:拆分成多个类

// 抽象基类定义职责
public abstract class AbstractAnimal
{
    protected string _Name;
    public abstract void Breath();  // 呼吸是谁的职责?各自子类的
    public abstract void Action();  // 行动是谁的职责?各自子类的
}

public class Chicken : AbstractAnimal
{
    public override void Breath() => Console.WriteLine($"{_Name} 呼吸空气");
    public override void Action() => Console.WriteLine($"{_Name} flying");
}

public class Fish : AbstractAnimal
{
    public override void Breath() => Console.WriteLine($"{_Name} 呼吸水");
    public override void Action() => Console.WriteLine($"{_Name} swimming");
}

实际案例:BankClient 拆分

以银行客户端为例,一个典型的违反 SRP 的写法是把所有逻辑堆在一个类里:

// 反例:所有职责混在一起
public class BankClient
{
    public void Run()
    {
        // 1. 验证用户
        // 2. 查询余额
        // 3. 计算利息
        // 4. 展示结果
        // 任何一个环节变化,都要改这个类
    }
}

正确做法是按变化原因拆分:

public class UserCheckService  { public bool CheckUser(string id) { ... } }
public class AccountService    { public decimal GetBalance(string id) { ... } }
public class InterestCalculator{ public decimal Calculate(decimal balance) { ... } }
public class DisplayService    { public void Show(decimal amount) { ... } }

验证逻辑可能从数据库换成远程接口,余额查询可能换数据源——变化原因不同,就该分开。

记忆口诀

一个类,一件事,改一处,不牵连。


二、OCP — 开闭原则

Open Closed Principle

核心思想

对扩展开放,对修改关闭。新增功能时,尽量不改已有代码,而是新增代码。

通俗理解

手机出了新功能,不是把旧手机拆了重装,而是出一款新型号。

四种扩展方式

以 Student.Study() 方法为例,当需要新增"免费学习"功能时,有四种做法,优劣各不同:

方式1:直接改原方法(最差)

public virtual void Study(bool isFree = false)
{
    if (isFree) Console.WriteLine("Study Free");
    else Console.WriteLine("Study Paid");
    // 改了原有逻辑,可能引入新 Bug,影响已有功能
}

方式2:在原类新增方法(次之)

public class Student
{
    public virtual void Study() => Console.WriteLine("Study Paid");
    public virtual void StudyFree() => Console.WriteLine("Study Free"); // 新增,不改原方法
}
// 缺点:原类仍然被修改,随着功能增多类会越来越臃肿

方式3:继承扩展(推荐)

public class StudentChild : Student
{
    public override void Study()
    {
        base.Study();
        Console.WriteLine("Study Review"); // 扩展行为
        // 完全不修改父类,通过继承扩展
    }
}

方式4:新增独立类(最彻底)

public class StudentFree  // 全新的类,完全不影响原有 Student
{
    public virtual void Study() => Console.WriteLine("Study Free");
}

记忆口诀

加功能,加代码;别动旧代码。


三、LSP — 里氏替换原则

Liskov Substitution Principle

核心思想

子类必须能够替换父类使用,且程序行为不变。换句话说:能用父类的地方,换成子类也没问题。

通俗理解

你叫了一辆出租车,来了一辆网约车,你还是能到达目的地——这就是替换。但如果来了一辆自行车,你就到不了——这就违反了替换原则。

C# 中的多态机制

理解 LSP 的关键是搞清楚 C# 里三种方法的行为差异:

// 父类引用指向子类对象
ParentClass instance = new ChildClass();

instance.CommonMethod();   // 调用的是父类的 CommonMethod(new 隐藏,不是多态)
instance.VirtualMethod();  // 调用的是子类的 VirtualMethod(override 覆写,多态生效)
instance.AbstractMethod(); // 调用的是子类的 AbstractMethod(抽象方法必须覆写)

三种方法的区别:

方法类型关键字子类能否覆写父类引用调用结果
普通方法无用 new 隐藏调用父类版本
虚方法virtual用 override调用子类版本(多态)
抽象方法abstract必须 override调用子类版本(多态)

什么时候不该继承?

继承要满足"is-a"关系。如果子类无法完整履行父类的所有契约,就不该继承。

以 People 和 Japanese 为例:假设 People 有一个 Traditional() 方法(传统礼仪),而 Japanese 的礼仪体系完全不同,强行继承后要么抛异常,要么调用方需要做类型判断——这就违反了 LSP。

// 违反LSP的写法
public class Japanese : People
{
    public override void Traditional()
    {
        throw new NotImplementedException(); // 被迫实现,但实际不支持
    }
}

// 正确:断掉不合适的继承关系,各自独立
public class Japanese
{
    public void Ninja() { ... }  // 只实现自己真正有的行为
}

判断是否该继承的简单方法: 把子类替换到所有使用父类的地方,程序行为是否仍然正确?如果不是,就不该继承。

记忆口诀

子类替父类,行为不能变;不是"是一种",就别乱继承。


四、ISP — 接口隔离原则

Interface Segregation Principle

核心思想

不要让一个类被迫实现它用不到的接口方法。接口要小而专,不要大而全。

通俗理解

你去餐厅只想点菜,不需要同时学会做菜、洗碗、收银。如果菜单上把这些都列出来要求你会,那就太荒谬了。

反例:大而全的接口

// 问题接口:包含了所有功能
public interface IExtendAll
{
    void Photo();   // 拍照
    void Online();  // 上网
    void Game();    // 游戏
    void Record();  // 录像
    void Movie();   // 看电影
    void Map();     // 地图
    void Pay();     // 支付
}

TV 类实现 IExtendAll 的问题:

public class TV : IExtendAll
{
    public void Online() { ... }  // 电视能上网 ✓
    public void Movie() { ... }   // 电视能看电影 ✓
    public void Photo() { throw new NotImplementedException(); }  // 电视不能拍照!被迫实现 ✗
    public void Pay()   { throw new NotImplementedException(); }  // 电视不能支付!被迫实现 ✗
}

正确做法:拆分成小接口

public interface IExtendPhoto  { void Photo(); }
public interface IExtendRecord { void Record(); }
public interface IExtendGame   { void Online(); void Game(); }

// 相机只实现它需要的接口
public class Camera : IExtendPhoto, IExtendRecord
{
    public void Photo()  { ... }
    public void Record() { ... }
    // 不需要实现 Online/Game/Pay 等
}

// 需要组合时,通过接口继承
public interface IExtendAll2 : IExtendPhoto, IExtendRecord, IExtendGame
{
    void Movie(); void Map(); void Pay();
}

ISP vs SRP 的区别

  • SRP 说的是类只做一件事
  • ISP 说的是接口只暴露需要的方法

记忆口诀

接口要小,按需实现;大接口拆小,各取所需。


五、DIP — 依赖倒置原则

Dependence Inversion Principle

核心思想

高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

通俗理解

你用充电器充电,插头是标准接口(抽象),不管是苹果还是安卓的充电器(具体实现),只要符合接口标准就能用。你不需要为每种手机买专用充电器。

反例:依赖具体类

public class Student
{
    // 每新增一款手机,就要在 Student 里加一个方法 —— 高层依赖低层
    public void PlayiPhone(iPhone phone) { phone.Call(); phone.Text(); }
    public void PlayLumia(Lumia phone)   { phone.Call(); phone.Text(); }
    public void PlayMi(Mi phone)         { phone.Call(); phone.Text(); }
    // 来了 Honor 怎么办?再加一个 PlayHonor?
}

正确做法:依赖抽象

// 抽象层
public abstract class AbstractPhone
{
    public abstract void Call();
    public abstract void Text();
}

// 高层模块依赖抽象,不依赖具体
public class Student
{
    public void PlayPhone(AbstractPhone phone)  // 参数是抽象类
    {
        phone.Call();
        phone.Text();
    }
}

// 新增手机只需新增类,Student 完全不用改
public class Honor : AbstractPhone
{
    public override void Call() { ... }
    public override void Text() { ... }
}

抽象类 vs 接口

DIP 中的"抽象"可以用抽象类或接口来实现,两者各有适用场景:

// 用接口实现 DIP
public interface IPhotoInterface
{
    void Photo();
    void Video();
}

// 高层只依赖接口,不关心具体实现
IPhotoInterface photo = new iPad();
photo.Photo();
photo.Video();
// 想换成其他设备?只需注入不同的实现类,上层代码不用改

抽象类和接口的选择:

  • 抽象类:有公共实现逻辑可以共享时用(比如多个手机品牌都有相同的 Show() 展示逻辑,放在抽象基类里共享)
  • 接口:纯约束,没有实现,多继承场景用(C# 不支持多继承,但支持实现多个接口)

记忆口诀

依赖抽象不依赖具体;新增功能加类,不改旧代码。


六、LOD — 迪米特法则

Law Of Demeter(也叫最少知识原则)

核心思想

一个对象应该对其他对象保持最少的了解。只和"直接朋友"说话,不和"陌生人"说话。

直接朋友指:当前对象本身、方法参数、方法返回值、成员变量。

通俗理解

你找工作,只需要联系 HR,不需要直接联系每个部门负责人。HR 帮你协调,你不需要了解公司内部结构。

反例:School 直接操作 Student

// 违反迪米特:School 直接操作 Student(陌生人)
public void ManageOther()
{
    foreach (Class c in ClassList)
    {
        // School 直接拿到 StudentList,直接操作 Student
        List<Student> studentList = c.StudentList;
        foreach (Student s in studentList)
        {
            Console.WriteLine(s.StudentName);  // School 不应该直接认识 Student
        }
    }
}

正确做法:通过直接朋友委托

// 遵循迪米特:School 只和 Class 说话
public void Manage()
{
    foreach (Class c in ClassList)
    {
        c.ManageClass();  // School 只调用 Class 的方法,不直接碰 Student
    }
}

// Class 负责管理自己的 Student
public void ManageClass()
{
    foreach (Student s in StudentList)
    {
        s.ManageStudent();  // Class 才是 Student 的直接朋友
    }
}

访问修饰符与迪米特

访问修饰符本质上也是迪米特法则的体现——控制"谁能知道什么":

  • private:只有自己知道(隐私)
  • protected:只有子类知道
  • internal:只有同一程序集知道
  • public:所有人都知道

暴露越少,耦合越低。 设计类时,默认从 private 开始,只在真正需要时才提升可见性。

记忆口诀

只和朋友说话,不认识的别搭理;中间人帮你传话,自己别乱串门。


六大原则总结对比

原则英文缩写一句话总结
单一职责SRP一个类只做一件事
开闭原则OCP扩展加代码,修改不动旧代码
里氏替换LSP子类能无缝替换父类
接口隔离ISP接口要小而专,不强迫实现无用方法
依赖倒置DIP依赖抽象,不依赖具体实现
迪米特法则LOD只和直接朋友交互,降低耦合

记忆口诀:SOLID + LOD,其中 SOLID = SRP + OCP + LSP + ISP + DIP

C# Xamarin For Android移动开发基础实战演练 一、课程介绍 英文原文:C# is the best language for mobile app development.Anything you can do in Objective-C, Swift or Java, you can do in C#。 中文译意:C#是移动应用程序开发的最佳语言。 在Objective-C,Swift或Java中你可以做的任何事情,你都可以在C#中完成... 阅读详情

相关推荐

【车载开发系列】ParaSoft单元测试环境配置(一)

bdf是数据文件的一种类型,表示一种数据库文件。在ParaSoft当中,可以使用bdf文件来快速创建一个测试项目。

千月星跡 2147

.NET面试题及答案详解

.NET面试题及答案详解C#1. 类和结构的区别?2. C#中类成员的访问修饰符有哪些?3. 面向对象的三大特征是什么?4. 抽象类和接口的区别?5. 接口可以包含哪些成员?6. 什么是装箱和拆箱?7. C#中,哪些类型是值类型?哪些类型是引用类型?8. 值类型和引用类型的区别?9. C#中的基本数据类型有哪些?10. 重写和重载的区别?方法的override和overload有什么区别?11. 构造函数是否可以被继承?是否可以被重写?12. C#中,运算符"?"和"??"的区别是什么?13. C#中,用`

视觉软件 1万+

three.js开发3D地图记录(二)

添加地图点击事件,点击区域,地图区域改变颜色并浮起来。

anwebDesigner的博客 1694

.NET 很好,你可能对它有一些误解

>作者:Charles Chen在 20 年前的 2002 年, 微软公布了下一代的软件、服务的愿景和路线,2 月 13 日,Visual Studio .NET 推出,.NET 开发平台的第一个版本正式向世界发布。到现在为止,.NET 都已经 20 岁了, 它已经成长为一个成熟稳定的平台。但是,我发现很多开发人员还是对 .NET 有一些偏见和误解,让我们来消...

zls365365的博客 145

先进封装技术 Part03---重布线层(RDL)的科普

RDL是一种在芯片封装过程中用于重新分布电气连接的技术。它通过在芯片表面或中介层上形成额外的布线层,重新分配芯片的I/O(输入/输出)位置,以适应不同的封装需求和提高电气连接的灵活性。

阿拉伯梳子的专栏 4697

『言善信』Fiddler工具 — 1、Fiddler介绍与安装

文章目录1、Fiddler简介2、Fiddler功能3、Fiddler工作原理4、Fiddler安装 1、Fiddler简介 Fiddler是强大的抓包工具之一,它能记录所有客户端和服务器之间的HTTP和HTTPS请求,是web调试的利器。 Fiddler是位于客户端和服务端之间的HTTP代理,它的原理是以web代理服务器的形式进行工作的,使用的代理地址是:127.0.0.1,端口默认为8888,我们也可以通过设置进行修改。 Fiddler可以抓取支持HTTP代理的任意程序的数据包,如果要抓取https会

繁华似锦Fighting 1463

主构造函数到底该不该用?C# 13新语法落地避坑清单,含6个生产环境崩溃案例与修复补丁

C# 13主构造函数优化助你精简代码、提升可维护性。本文解析适用场景、6个真实生产崩溃案例及修复补丁,涵盖参数验证、依赖注入、序列化兼容等关键实践。规避隐式行为风险,显著降低初始化错误率,值得收藏。

ProceSeed的博客 379

手把手教你用X-AnyLabeling实现医学图像标注:从SAM模型选择到多边形优化全流程

本文详细介绍了如何使用X-AnyLabeling平台结合Med-SAM等专业模型实现医学图像的高效标注。从环境配置、模型部署到实战工作流,涵盖智能辅助标注、低对比度解决方案及团队协作方案,帮助医疗从业者大幅提升标注效率与质量。特别适合处理CT/MRI等医学影像的复杂标注需求。

weixin_29251131的博客 455

C# Xamarin移动开发基础进修篇

C# Xamarin移动开发基础进修篇 一、课程介绍 英文原文:C# is the best language for mobile app development.Anything you can do in Objective-C, Swift or Java, you can do in C#。 中文译意:C#是移动应用程序开发的最佳语言。 在Objective-C,Swift或J...

cxu123321的博客 338

数学建模--十大经典常用算法

数学建模十大算法:灰色预测,蒙特卡洛,遗传算法,模拟退火法,组合算法,神经网络,动态规划,聚类算法,类比法,概率算法,Floyd算法

qq_51533426的博客 5646

《Python星球日记》 第43天:机器学习概述与Scikit-learn入门

《Python星球日记》 第43天:机器学习概述与Scikit-learn入门

Code_流苏:在代码中寻诗意,在实践中觅真知 932

C# Xamarin For Android移动开发基础进修篇

一、课程介绍 英文原文:C# is the best language for mobile app development.Anything you can do in Objective-C, Swift or Java, you can do in C#。 中文译意:C#是移动应用程序开发的最佳语言。 在Objective-C,Swift或Java中你可以做的任何事情,你都可以在C#...

跟着阿笨一起玩NET 2万+

COSMO-RS理论:流体相热力学预测的核心模型与技术突破

摘要:COSMO-RS模型通过量子化学计算分子表面电荷分布,实现了从微观量子尺度到宏观化工过程的热力学预测。该模型基于介电连续介质理论,将分子表面片段化处理,通过σ-剖面描述电荷分布,并计算片段间的静电失配能和氢键能。其核心算法通过自洽化学势方程求解活度系数,克服了传统模型依赖实验参数的局限。随着技术进步,模型引入了高阶描述符和色散力修正,提升了预测精度。在药物研发、化工分离等领域广泛应用,但仍存在对长程有序结构预测不足等局限性。该模型为复杂体系的热力学性质预测提供了重要理论工具。

ASKCOS博客 971

FastReport开源报表工具:终极.NET数据可视化开发完全指南

FastReport是一款功能强大的开源报表工具,专为.NET 6/.NET Core/.NET Framework开发,帮助开发者快速生成专业级报表。无论你是需要简单的列表报表还是复杂的商业智能分析,FastReport都能提供完整的数据可视化解决方案。 ## 项目概览:为什么选择FastReport? 你是否曾为项目中的报表生成而烦恼?传统的报表开发往往需要大量编码工作,而FastRepo

gitblog_01154的博客 97

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

asp.net+C#进销存管理系统

管理员主要是对于系统的基本信息维护和综合信息管理几个模块,主要功能包括:货物类别、货物信息、仓库管理、供应商、客户管理等;(7)库存浏览:浏览库存中的货物信息本asp.net进销存管理系统基于web,是专用于毕业设计的源码,主要分为六大模块:采购模块,销售模块,查询模块,统计模块,用户管理模块,信息管理模块。(8)采购信息列表:根据不同的仓库过滤查询采购信息列表功能,可以将采购信息导出报表。(2)客户维护:维护系统的客户信息功能,可以添加、修改和删除客户信息。

我的个人的博客 645

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

面试的六大步骤,前端人如何在面试中快速破局!,如何学web前端开发

关于缓存策略也是面试中的热门问题,其实缓存分为强缓存和协商缓存。当浏览器进行资源请求时,会判断是否命中强缓存,如果命中则直接从本地缓存读取,不会向服务器发送请求。当强缓存没有命中或者请求头中设置了不走强缓存时,会发送请求到服务器,判断协商缓存是否命中。如果命中,服务器将请求返回,加载浏览器缓存;如果没有命中,服务器则直接将资源返回。讲了这么多,相信大家对技术本质的认知有了一定的提高。那么究竟该如何从面试中破局?首先是要学好基础知识,然后能够对这些网红框架轻松地进行庖丁解牛。

weixin_58134620的博客 735

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

2026 PDF Generation API 全面对比评测:13 款主流方案横向测评 (HTML转PDF)

PDF 生成 API (HTML转PDF),PDF 生成 API 市场已从"能不能生成"进化到"生成得好不好、快不快、安不安全"。本文从多个维度进行横向对比,帮助技术团队做出最优选型。

weixin_66974439的博客 238

OpenClaw.NET .NET 原生插件开发完全指南:以 Mempalace 插件为范例

是 .NET 原生插件的元数据声明文件,其结构与传统 TypeScript 插件的},},和typeName字段共同定位插件的入口点,前者指定 DLL 文件的相对路径,后者指定实现接口的完整类型名称,包括命名空间。数组声明了插件提供的能力类型,此处为["memory"],表明这是一个记忆提供者插件,网关会根据此声明将插件纳入记忆服务的候选列表。jitOnly标志是一个关键的设计决策,设置为true表明该插件当前仅支持 JIT 动态加载模式,尚未完成 NativeAOT 兼容性适配。

Cd9GnrGfH的博客 275

OpenClaw.NET 插件系统架构解析

是 .NET 原生插件的元数据声明文件,其结构与传统 TypeScript 插件的。

InU1ns03f的博客 260

aspnet163百货商店超市库存管理系统 C#进销存管理系统

商品库存管理系统主要分为七大模块:采购管理模块,销售管理模块,系统功能设定,库存管理模块,基本资料维护,报表生成模块、查询统计模块等。该模块为系统人员定期对库存货物的现有情况进行全方位的了解,及时的核对库存中货物数量,以及调整由于其他不可抗力的原因造成的货物损失,损坏,以及人为计算错误等造成的库存货物与实际不符的情况,及时更正库存货物数据信息。该模块为系统用户及时了解各个库存货物状况,如果一个库存出现货物紧缺,可以及时从其他的库存调入货物,做到高效率的货物运转,提供了良好的帮助。

我的个人的博客 656
上一篇: 12-C#.Net-加密解密-学习笔记
下一篇: 14-C#.Net-设计模式-学习笔记
似水明俊德
博客等级 码龄8年 247粉丝 276原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值