设计模式六大原则 SOLLID 接口抽象类得选择
在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期引入,指代了面向对象编程和面向对象设计的五个基本原则。当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能。
首字母 | 指代 | 概念 |
---|---|---|
S Single Responsibility Principle | 单一功能原则 | 认为“对象应该仅具有一种单一功能”的概念。 |
O Open Closed Principle | 开闭原则 | 认为“软件应该是对于扩展开放的,但是对于修改封闭的”的概念。 |
L Liskov Substitution Principle | 里氏替换原则 | 认为“程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的”的概念。
参考契约式设计。 |
I Interface Segregation Principle | 接口隔离原则 | 认为“多个特定客户端接口要好于一个宽泛用途的接口”[5] 的概念。 |
D Dependence Inversion Principle | 依赖倒置原则 | 认为一个方法应该遵从“依赖于抽象而不是一个实例”[5] 的概念。 依赖注入是该原则的一种实现方式。 |
L Law Of Demeter | 迪米特原则 |
1 单一职责原则(SRP)
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中,
应该只有一个引起它变化的原因(甲类负责两个不同的职责:职责A,职责B。当由于职责A需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责B功能发生故障。也就是说职责A和B被耦合在了一起”)。
例子 多分支的Animal该如何设计 (猫狗 鸟 鱼)
都放在一个类---需要各种判断+分支---肯定不稳定,有很多个原因让他变化---违背了 单一职责---
拆分成多个类(chicker cow fish)---每个类就都稳定了,都单一职责了 同一父类多个类型,其实应该拆开写,而不是都塞在一个类---满足单一职责
总结 1 如果类型足够简单,可以在类级别去违背单一职责
2 如果方法足够简单,可以在方法级别去违背单一职责
3 如果类型复杂了,方法逻辑多了,建议遵循单一职责原则
4 一个方法,不超过50行(编码建议) 5 一个类,不超过300行(编码建议)
2 开放封闭原则(OCP)
实体应该对扩展是开放的,对修改是封闭的。即可扩展(extension),不可修改(modification)。
eg:
原代码,不同用户类型进行不同服务,但是后续每新增不同的用户类型,只能在下面继续加判断代码。
修改后代码,用户实现统一的接口,后续新增用户类型,只需要新增对应实现类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class Student { public int Id { get ; set ; } public string Name { get ; set ; } public virtual void Study() { Console.WriteLine($ "This is {this.Id}-{this.Name} Study VIP" ); //1 增加参数,增加判断,增加分支 //2 增加额外逻辑--直接改方法 } #region 新增方法1 public virtual void StudyFree() { Console.WriteLine($ "This is Student Study Free" ); //这个比修改方法强---改动类 } #endregion #region 新增方法2 public virtual void StudyWithVideo() { this .Study(); Console.WriteLine( "课程视频课件代码回看" ); //不需要修改原有方法 } #endregion } /// <summary> /// 新增类1 /// </summary> public class StudentFree { public int Id { get ; set ; } public string Name { get ; set ; } public virtual void Study() { Console.WriteLine($ "This is {this.Id}-{this.Name} Study Free" ); //完全不影响原有的类 } } /// <summary> /// 新增类2 /// </summary> public class StudentChild : Student { public override void Study() { base .Study(); Console.WriteLine( "课程视频课件代码回看" ); } //不需要修改类 } //新增类库替换 |
满足开放封闭原贼得方法
1 2 3 4 5 6 | 满足OCP的程度排序: 1 修改配置—IOC/工厂使用配置文件 2 增加dll—依赖抽象+反射 3 增加 class -- 4 增加方法— 5 修改方 |
3 里氏替换原则(LSP)
一个对象在其出现的任何地方,都可以用子类实例做替换,并且不会导致程序的错误。
经典的例子: 正方形不是长方形的子类。原因是正方形多了一个属性“长 == 宽”。这时,对正方形类设置不同的长和宽,计算面积的结果是最后设置那项的平方,而不是长*宽,从而发生了与长方形不一致的行为。如果程序依赖了长方形的面积计算方式,并使用正方形替换了长方形,实际表现与预期不符。
1 2 3 4 5 6 7 | People people = new Chinese() 1 虚方法:调用的是子类-是以右边类型为准-以 override 为准---所以一定得有有个 关键字, virtual / abstract 2 抽象方法:调用的是子类-是以右边类型为准-以 override 为准---所以一定得有有 个关键字, virtual / abstract 3 普通方法:调用的是父类-是以左边类型为准-是以编译期为准,因为二次也不知道 运行时类型,所以要提前指定 |
1 父类有的,子类是必须有的:否则就断掉继承 People-Japanese
2 子类可以有自己的属性和行为:People-Chinese-Hubei
3 父类实现的东西,子类就不要再写了:Chinese-Hubei的SayHi,写了可能造成不同 的行为---如果预期变化,就加上virtual/abstract
4 声明属性、字段、变量,尽量声明为父类
尽量声明的时候用父类,
子类尽量不要篡改父类的行为,或者virtual/abstract
4 接口隔离原则(ISP)
接口隔离原则表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中的方法分组,然后用多个接口替代它,每个接口服务于一个子模块。
简单地说,就是使用多个专门的接口比使用单个接口要好很多。 -------简单说就是吃多少饭 ,就拿多少,不要一下拿很多碗过来
一定要适度。 可以通过 接口继承来组合接口
ISP的主要观点如下:
1)一个类对另外一个类的依赖性应当是建立在最小的接口上的。
ISP可以达到不强迫客户(接口的使用方法)依赖于他们不用的方法,接口的实现类应该只呈现为单一职责的角色(遵循SRP原则)
ISP还可以降低客户之间的相互影响---当某个客户要求提供新的职责(需要变化)而迫使接口发生改变时,影响到其他客户程序的可能性最小。
2)客户端程序不应该依赖它不需要的接口方法(功能)。
客户端程序就应该依赖于它不需要的接口方法(功能),那依赖于什么?依赖它所需要的接口。客户端需要什么接口就是提供什么接口,把不需要的接口剔除,这就要求对接口进行细化,保证其纯洁性。
1 2 3 4 | 接口隔离和单一职责的区别: 其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。 其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和 细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。 |
5 依赖倒置原则(DIP)
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对抽象(接口)编程,而不是针对实现细节编程。
开闭原则(OCP)是面向对象设计原则的基础也是整个设计的一个终极目标,而依赖倒置原则(DI )则是实现OCP原则的一个基础,换句话说开闭原则(OCP)是你盖一栋大楼的设计蓝图,那么依赖倒置原则就是盖这栋大楼的一个钢构框架。
来看一个例子假设我们在开发一个软件产品需要一个日志系统,要将系统产生的一些重要事情记录在记事本上。通常我们的实现如下:
但是随着时间的推移,产品做的好买了很多客户,产品变得越来越大,使用Logger 类的地方成千上万处,可怕的事情终于发生了:
A 客户提出来我想把日志存在数据库中便于做统计分析。
B 客户说我想把日志打印在一个控制台上便于我时时监测系统运行情况。
C 客户说我要把日志存到Windows Azure Storage上。
怎么办呢? 回过头来看看我们的这个日志系统的设计才恍然大悟:没有遵守面向对象设计原则的依赖倒置原则和开闭原则了。知道就好,找到法门了, 我们将日志这一块的设计重构一下让其符合OCP和DIP应该就可以了。 那么我们就要首先抽象写日志的接口ILog, 让实际调用的地方调用高层抽象(ILog),具体的实现类TextLogger,ConsoleLogger,DatabaseLogger,AzureStorageLogger都继承自ILog接口,然后我们在利用反射加配置,不同的用户配置不同的具体实现类,这样问题就迎任而解了。
六 迪米特法则(Law of Demeter)又叫作最少知识原则
一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽 可能少的了解,只和朋友通信,不和陌生人说话。
比如 一个学校有校长 老师 学生 老师知道学生 校长知道老师 高层得校长不需要直接知道学生 ,高层同过中间层 控制下层
这样降低了耦合 但是增加了中间层, 怎么选择看自己得业务逻辑
7接口抽象类得选择
抽象类: abstract 可以包含具体实现---约束+实现
接口: interface 不能包含具体实现---约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | C#8.0变化 1 方法可以声明 public 也可以 protected ,但不能是 private 2 字段还是不能声明 3 以前委托和类不能声明,现在都可以声明了 4 以前不能有具体实现,现在也可以有了 以前都说接口不能变,现在可以直接增加实现 高层不需要实现接口 抽象类:(父类只有一个) 继承+约束: 其实为了继承,约束是副业 is ---抽象类表述的对象 接口: (接口可以多个) 只是做约束 can do ,就可以跨不同类型 ----接口表述的是行为,以及规则 更多时候,选择接口就对了, |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现