SOLID是五大设计原则的首字母简写,最早出现于出自Robert Martin(罗伯特. 马丁)的《架构整洁之道》第三章设计原则。他们分别是

single Responsibility Principle:单一职责原则
Open Closed Principle:开闭原则
Liskov Substitution Principle:里氏替换原则
Interface Segregation Principle:接口隔离原则
Dependence Inversion Principle:依赖倒置原则

再加上后来的组合聚合、迪米特法则,就构成了我们软件设计的七大原则。

一:单一职责

单一职责原则(SRP:Single responsibility principle)又称单一功能原则,它规定一个类应该只有一个发生变化的原因。对于一个类来说,应该只有一个引起它变化的原因。单一职责降低了类功能的耦合程度,如果一个类有两个或者更多的职责,一个职责的改变往往会引起连锁反应,出现意想不到的错误。

下面我们用具体的代码来进行分析。

业务:假设现在我们有很多种交通工具,这些交通工具都要支持运行。

不符合单一职责的代码:

    internal class Program{//程序开始时的main方法static void Main(string[] args){//声明对象进行方法的调用Vehicle vehicle = new Vehicle();vehicle.run("直升飞机");vehicle.run("航天飞机");//代码到这里是没有问题的,两种飞机在天空上进行行驶,可如果我们来了新的需求新增另外类型的交通工具vehicle.run("出租车");vehicle.run("轮船");//很显然出租车和轮船是不在天空上进行行驶的。Vehicle类的run方法不适合。其原因就是run包含的职责//太多了,实际上它包含了多种运行的职责。}}//创建一个交通工具类public class Vehicle{//实现一个交通工具运行的方法public void run(string vehicleType){Console.WriteLine(vehicleType+"在天空中运行");}}

针对上面存在的问题对我们的代码进行改进

符合单一职责的代码

    internal class Program{static void Main(string[] args){//使用接口进行调用IVehicle airVehicle = new AirVehicle();airVehicle.run("直升飞机");airVehicle.run("航天飞机");//将职责分成三个类,每个类负责不同的run的实现,调用的时候可以直接根据对应对象进行调用IVehicle roadVehicle = new RoadVehicle();roadVehicle.run("出租车");IVehicle waterVehicle = new WaterVehicle();waterVehicle.run("轮船");//此时不会因为职责的冗余而产生错误。并且当我们想要添加新的交通工具时,让它实现跑的接口。直接调用就可以了,也更好的支持了开闭原则}}//声明一个交通工具的接口,里面包含一个跑的方法internal interface IVehicle{void run(string vehicle);}//下面的类分别实现跑的方法class RoadVehicle :IVehicle{public void run(String vehicle){Console.WriteLine(vehicle + " 在公路运行...");}}class AirVehicle : IVehicle{public void run(String vehicle){Console.WriteLine(vehicle + " 在天空运行...");}}class WaterVehicle :IVehicle{public void run(String vehicle){Console.WriteLine(vehicle + " 在水上运行...");}}

为了方便读者阅读,将类图附在下方。

2:开闭原则

开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它源代码的前提下变更它的行为。

下面我们用具体代码来进行实现

业务:假设有一个水果仓库,对苹果、香蕉、两种水果进行存储,现在新增第三种水果梨,进行入库。

错误代码:

internal class FruitStorage{//进行入库的方法,将具体的水果进行传入public void Warehousing(Fruits fruits){if (fruits.FruitsType =="1")    //如果是第1种类型就调用苹果入库方法{AppleWarehousing();}else if (fruits.FruitsType =="2")       //如果是第2种类型就调用香蕉入库方法{BananWarehousing();}PearWarehousing();      //如果是第3种类型就调用香蕉入库方法}//具体进行苹果入库的方法public void AppleWarehousing(){Console.WriteLine("苹果入库了");}//具体进行香蕉入库的方法public void BananWarehousing(){Console.WriteLine("香蕉入库了");}//上面两个是原本写的方法,后面为新增的方法public void PearWarehousing(){Console.WriteLine("梨入库了");}}public class Fruits{//水果类型属性public string FruitsType;}//苹果类public class Apple :Fruits{//构造函数给水果类型赋值public Apple(){FruitsType = "1";}}//香蕉类public class Banana : Fruits{public Banana(){FruitsType= "2";}}//梨类public class Pear : Fruits{public Pear(){FruitsType = "3";}}

为了方便观看,附上类图

在上述代码中,我们增加需求,添加了Pear类时,需要更改FruitStorage类的Warehousing方法,添加新的分支,不符合开闭原则。

那么我们应该如何改造成符合开闭原则的代码呢?

    internal class FruitStorage{//进行入库的方法,将具体的水果进行传入public void Warehousing(Fruits fruits){fruits.FruitsWarehousing();     //调用具体的水果类}}public interface IFruits{void FruitsWarehousing();}//苹果类public class Apple : IFruits{public void FruitsWarehousing(){Console.WriteLine("苹果入库了");}}//香蕉类public class Banana : IFruits{public void FruitsWarehousing(){Console.WriteLine("香蕉入库了");}}//梨类public class Pear : IFruits{public void FruitsWarehousing(){Console.WriteLine("梨入库了");}}

·         新的代码中,我们将具体入库方法的实现放在了水果类,当新增加一个水果类的时候,要实现相应的入库方法,通过添加子类的方式新增类型,不需要进行代码修改。

3:里氏替换

派生类(子类)对象可以在程式中代替其基类(超类)对象。要求子类不能对父类的方法进行重写,因为子类很可能改变父类的意思而引入未知错误。

举一个例子,假设有一对唱京剧的父子,儿子会父亲唱的所有的戏,并且还会父亲不会的新式戏曲。某天父亲无法上台,但是戏要必须唱。根据里氏替换,儿子可以代替父亲上台,但是只能唱父亲会的戏,不能唱自己会的。并且不可对父亲的戏进行改编。(如果不遵守,则子类无法完全扮演父类的角色。)

代码:

    //唱京剧的父亲public abstract class Father{//父亲会的戏public void BeijingOpera1(){Console.WriteLine("京剧1的实现");}//父亲会,可以被儿子改编的戏public virtual void BeijingOpera2(){Console.WriteLine("京剧2的实现");}}//唱京剧的儿子public class son : Father{public override void BeijingOpera2(){// base.BeijingOpera2();Console.WriteLine("京剧2子类特殊的实现");}}

这里子类就更改了父类的方法,使得子类不能代替父类上台演唱,在里氏替换里,这是不被允许的。

4:接口隔离原则

不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次接口。

接口隔离有两个方面的重点。第一个是一个类对另外一个类的依赖性应当是建立在最小的接口上的,要求类之间通过最小接口通信。第二是这个接口一定要足够小,不让实现这个接口的类去实现不使用的方法。

业务:

定义一个支付接口,某用户要通过支付接口,进行支付。一共有三种支付方式,分别是微信支付、支付宝支付、还有现金支付。

错误的代码:

//支付接口internal interface IPay{//微信支付void WeChatPay();//支付宝支付void AliPay();//现金支付void CashPay();}//普通顾客public class OrdinaryCustomers : IPay{public void AliPay(){Console.WriteLine("支付宝支付的具体实现");}public void CashPay(){Console.WriteLine("现金支付的具体实现");}public void WeChatPay(){Console.WriteLine("微信支付的具体实现");}}//互联网顾客,无法使用现金支付public class InternetCustomers : IPay{public void AliPay(){Console.WriteLine("支付宝支付的具体实现");}public void CashPay()       //虽然无法使用现金支付,但是还是要给出现金支付的实现。{throw new NotImplementedException();}public void WeChatPay(){Console.WriteLine("微信支付的具体实现");}}

上述代码中,明明互联网用户无法进行现金支付,可是还是要给出具体的现金接口的实现,不符合接口隔离。我们应当将接口拆开,让其粒度最小。

   //微信支付接口internal interface IWeChatPay{void WeChatPay();}//支付宝支付接口internal interface IAliPay{void AliPay();}//现金支付接口internal interface ICashPay{void CashPay();}//普通顾客public class OrdinaryCustomers : IAliPay,ICashPay,IWeChatPay{public void AliPay(){Console.WriteLine("支付宝支付的具体实现");}public void CashPay(){Console.WriteLine("现金支付的具体实现");}public void WeChatPay(){Console.WriteLine("微信支付的具体实现");}}//互联网顾客,无法使用现金支付public class InternetCustomers : IAliPay,IWeChatPay{public void AliPay(){Console.WriteLine("支付宝支付的具体实现");}public void WeChatPay(){Console.WriteLine("微信支付的具体实现");}}

为了方便观看,附上类图:

将接口粒度分小之后,下面的类就可以根据自己的业务需求(支付方式)进行支付。

5:依赖倒置原则

程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

什么是抽象呢?抽象可以理解为抽“像”,就是将业务里像的东西抽出来,放在一起。把握住事物的本质,将本质抽出来。

这里的业务可以于开闭原则结合,读者可向上翻看开闭原则代码。在开闭原则的代码中,实际上,上层的FruitStorage(商品入库)类是依赖于下面的Apple等水果类的,下方各个水果类的改变将会直接影响上层FruitStorage(商品入库)类的调用。

符合依赖倒置的编写方式,是应该让两者都依赖于抽象,也就是下面的正确的写法,依赖于抽象的IFruits接口,降低客户和模块间的耦合。让代码符合开闭原则。

正确版本类图:

六:组合聚合原则

要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

利用原有的类来产生新的类的方式有两种,一种是继承,另一种是组合聚合。在六大关系中,继承是耦合性最强的类,耦合性越强,当代码发生变化时产生的影响就越大。

    public class Person{public void sayHello(){Console.WriteLine("打招呼方法的实现");}}public class User : Person{}public class Operator : Person{}

这里使用继承,让两种用户继承了人类,如果我们继续使用继承的话,每一个用户只能继承一种角色,但是实际上一个用户可以即是普通用户又是管理员,与业务不符所以可以新增角色,这个角色由原有的的类组合/聚合而成。

七:迪米特原则

迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解。

迪米特原则规定两个类尽量不要发生关系如果非要发生关系也使用友元类进行通信,以此来降低类之间的耦合。

软件设计原则SOLID+组合聚合+迪米特原则(附代码讲解)相关推荐

  1. 设计模式六大原则(5)——迪米特原则

    定义:一个对象应该对其他对象保持最少的了解. 问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大. 解决方案:尽量降低类与类之间的耦合. 自从我们接触编程开始,就 ...

  2. 软件设计原则之接口隔离原则、合成复用原则、迪米特原则

    系列文章目录 软件设计原则之单一职责原则.开闭原则 软件设计原则之里氏替换原则.依赖倒置原则 软件设计原则之接口隔离原则.合成复用原则.迪米特原则 文章目录 系列文章目录 一.接口隔离原则 什么是接口 ...

  3. 软件设计的开发原则-SOLID

    软件设计的开发原则-SOLID 开发时遵循以下原则可提高代码重用性.可读性.可靠性.可维护性 单一职责原则:高内聚.低耦合的指导方针.一个类只有一个引起它变化的原因,一个类只负责一项职责.一个方法尽量 ...

  4. 【设计模式】软件设计七大原则 ( 迪米特原则 | 代码示例 )

    文章目录 一.迪米特原则简介 二.迪米特原则代码示例 ( 反面示例 ) 1.经理类 2.员工类 3.商品类 4.测试类 三.迪米特原则代码示例 ( 推荐用法 ) 1.经理类 2.员工类 3.商品类 4 ...

  5. 六大设计原则之迪米特原则

    迪米特原则的定义 迪米特原则(Law of Demeter,LoD),也叫最少知识原则(Low knowledge Principle,LKP): 一个对象应该对其他对象有最少的了解. 通俗的讲:一个 ...

  6. 软件设计的七大设计原则

    一.前言 七大设计原则是23种设计模式的基础,体现了软件设计的思想,但并不是所有设计模式都遵循这七大设计原则,有些设计模式只遵循一部分设计原则,是对一些实际情况做的一些取舍.在我们项目中也并不一定完全 ...

  7. 《盘点软件设计中的七大原则》

    说在前头:本人为大二在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,能力有限,文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正.若 ...

  8. 大家一起学面向对象设计模式系列Chapter 02 软件设计的基本原则

    我们为什么要使用设计模式呢?有人可能会说为了设计出"高内聚低耦合"的软件."高内聚低耦合"的软件实际上也就是本文所说的具有可维护性和可复用性的软件. 这篇文章主 ...

  9. 编码灵魂(2)-迪米特原则

    引言 我觉得编码是有灵魂的,就像每个人都有信仰一样.那么如何去体现信仰,如何凸显灵魂就需要依赖它所固有的原则.最近学习了设计模式的六大原则,有所感悟,特此做总结和记录.在本文中详细介绍了迪米特原则(L ...

最新文章

  1. Python学习十四:filter()
  2. 微服务注册中心的选型和思考
  3. 编辑docker容器中的文件
  4. 《剑指offer》按之字行顺序打印二叉树
  5. Synchronized 关键字的用法
  6. dbnetlib sqlserver不存在或拒绝访问_404:对不起,您访问的网页不存在
  7. 7的整除特征 三位一截_茅台酒的合格证有哪些特征和鉴别要点?
  8. 决策过程并举例_成本效益分析举例
  9. python:rs, ws, es = select.select(inputs, [], []) --报错error 10022
  10. ch2 gpio应用:Buzzer封装
  11. 输出星期名缩写python_python练习题5.1输出星期名缩写
  12. GIT提交错分支,push错分支怎么办
  13. 近期工作心得(总结篇)
  14. 三线制Pt100隔离器在掘进机电机保护系统中的应用
  15. 1.1.2 Linux epoll详解
  16. 【Trie图】Hiho4_Hihocoder
  17. 飞利浦zigbee智能灯泡的软硬件设计
  18. 如何从Win10升级到Windows11正式版
  19. 4个惨烈冤案背后的司法真相
  20. 劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(三)

热门文章

  1. Freesurfer Mac版本安装过程及教程资源收集
  2. 发布移动App应用,Android应用市场发布渠道
  3. CAD看图移动端为什么这么火?
  4. ES6 进阶:你不知道的 Rest 参数与 Spread 语法细节
  5. MERCURY 1300M 11AC Linux驱动编译
  6. excel统计出现次数
  7. html精灵图的hover状态,css图片精灵图标怎么使用?
  8. 水题:A+B;简单等差数列求和;简单字符串处理;电梯题
  9. 凡技工具分享——凡技鼠标连点器 V1.1.exe
  10. 微信小程序 | 基于ChatGPT实现模拟面试小程序