博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式六大原则【单一职责】【里氏替换】【迪米特法则】【依赖倒置原则】【接口隔离原则】【开闭原则】...
阅读量:5061 次
发布时间:2019-06-12

本文共 8537 字,大约阅读时间需要 28 分钟。

设计模式:面向对象语言开发过程中,遇到种种的场景和问题,提出的解决方案和思路,沉淀下来,设计模式是解决具体问题的套路

设计模式六大原则:面向对象语言开发过程中,推荐的一些指导性原则,这些是没有明确的招数的,而且也经常被忽视或者违背!

 

一:单一职责原则(Single Responsibility Principle)

单一职责原则就是一个类只负责一件事儿,面向对象语言开发,类就是一个最基本的单位,单一职责的原则就是封装的粒度,主要关注的单个类实现的功能

比如我们下面的例子

1 ///  2 /// 封装 3 /// 动物类 4 /// 简单意味着稳定 5 ///  6 public class Animal 7 { 8     private string _Name = null; 9     public Animal(string name)10     {11         this._Name = name;12     }13    14     //应该拆分了15     public void Action()16     {17         if (this._Name.Equals("鸡"))18             Console.WriteLine($"{this._Name} flying");19         else if (this._Name.Equals("牛"))20             Console.WriteLine($"{this._Name} walking");21         else if (this._Name.Equals("鱼"))22             Console.WriteLine($"{this._Name} Swimming");23         else if (this._Name.Equals("蚯蚓"))24             Console.WriteLine($"{this._Name} Crawling");25     }26 }

我们声明一个动物,但是每个动物的action是不一样的,顺着我们的思维模式,我们会在action中增加对应的if来判断,不同的动物有不同的动作,

类似于这样的,在一个方法中写分支判断,然后执行不同的逻辑,这就违背了单一职责原则,但是其实我们想要的功能完全能实现,如果种类比较少且不变的情况下,我们完全可以这样操作,但是如果种类比较多且经常容易发生改变,那我们这样写就有很大的隐患,因为其中的改变有可能会影响到其它的。

我们可以对其进行改变,比如我们可以先创建一个基类

1  public abstract class AbstractAnimal 2  { 3      protected string _Name = null; 4      public AbstractAnimal(string name) 5      { 6          this._Name = name; 7      } 8  9      public abstract void Breath();10      public abstract void Action();11  }

然后可以创建不同动物的类来继承于这个基类

public class Fish : AbstractAnimal{    public Fish() : base("鱼")    {    }    public override void Breath()    {        Console.WriteLine($"{base._Name} 呼吸水");    }    public override void Action()    {        Console.WriteLine($"{base._Name} swimming");    }}
public class Chicken : AbstractAnimal{  public Chicken() : base("鸡")    {    }    public override void Breath()    {        Console.WriteLine($"{base._Name} 呼吸空气");    }    public override void Action()    {        Console.WriteLine($"{base._Name} flying");    }}

类似于这样的,然后在自己的类中实现自己的方法,一个类只负责自己的事情,且都比较单一,简单意味着稳定,意味着强大,这就是所谓的单一职责原则,那么究竟什么时候回使用单一职责原则呢?如果类型复杂,方法多,这样建议使用单一职责原则!

那么使用单一职责原则也有自己的弊端,具体分为以下两个方面

1:代码量的增加(拆分开类的代码明显比之前增加)

2:使用成本就是所谓的理解成本增高(调用者要晓得不同的类)

具体的单一原则分为以下五种

1:方法级别的单一职责原则:一个方法只负责一件事儿(职责分拆小方法,分支逻辑分拆)

2:类级别的单一职责原则:一个类只负责一件事儿
3:类库级别的单一职责原则:一个类库应该职责清晰
4:项目级别的单一职责原则:一个项目应该职责清晰(客户端/管理后台/后台服务/定时任务/分布式引擎)
5:系统级别的单一职责原则:为通用功能拆分系统(IP定位/日志/在线统计)

 

二: 里氏替换原则(Liskov Substitution Principle)

任何使用基类的地方,都可以透明的使用其子类,这主要是指 继承+透明(安全,不会出现行为不一致)

继承:子类拥有父类的一切属性和行为,任何父类出现的地方,都可以用子类来代替,主要是因为:

1:父类有的,子类是必须有的(私有不继承);如果出现了子类没有的东西,那么就应该断掉继承;、

2:子类可以有自己的属性和行为,但是子类出现的地方,父类不一定能代替

3:父类实现的东西,子类就不要再写了,(就是不要new隐藏),如果想修改父类的行为,通过abstract/virtual

举个例子:

1 public class People 2 { 3     public int Id { get; set; } 4     public string Name { get; set; } 5  7     public void Traditional() 8     { 9         Console.WriteLine("仁义礼智信 温良恭俭让 ");10     }11 }12 13 public class Chinese : People14 {15     public string Kuaizi { get; set; }16     public void SayHi()17     {18         Console.WriteLine("早上好,吃了吗?");19     }20 21 }22 23 public class Hubei : Chinese24 {25     public string Majiang { get; set; }26     public new void SayHi()27     {28         Console.WriteLine("早上好,过早了么?");29     }30 }

调用的时候:

{    Chinese people = new Chinese();    people.Traditional();    people.SayHi();}{    Chinese people = new Hubei();    people.Traditional();    people.SayHi();}{    var people = new Hubei();    people.Traditional();    people.SayHi();}

上面需要注意的是:如果是普通的方法,以左边为主,就是左边是什么类,就调用谁的普通方法(编译时决定),如果是abstract或者virtual则是以右边为主(运行时决定),所以:父类有的方法,子类就不要再写了,(就是不要new隐藏),如果想修改父类的方法,通过abstract/virtual来标识!

三:迪米特法则

也叫最少知道原则,就是:一个对象应该对其他对象保持最少的了解,只与直接的朋友通信。

他主要的职责就是关注类与类之间的交互,降低类与类之间的耦合,尽量避免依赖更多的类型

举例说明:

有一个学生类,班级类,学校类

/// /// 学生/// public class Student{    public int Id { get; set; }    public string StudentName { get; set; }    public int Height { private get; set; }    public int Salay;    public void ManageStudent()    {        Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.StudentName);    }}
///  /// 班级 ///  public class Class {     public int Id { get; set; }     public string ClassName { get; set; }     public List
StudentList { get; set; } public void ManageClass() { Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.ClassName); foreach (Student s in this.StudentList) { s.ManageStudent(); } } }
1 ///  2 /// 学校 3 ///  4 public class School 5 { 6     public int Id { get; set; } 7     public string SchoolName { get; set; } 8     public List
ClassList { get; set; } 9 10 public void Manage()11 {12 Console.WriteLine("Manage {0}", this.GetType().Name);13 foreach (Class c in this.ClassList)14 {15 //遵循了迪米特,school直接跟自己的朋友classList通讯,而不是跟自己的朋友的朋友通讯16 c.ManageClass();17 18 #region 违背了迪米特法则(跟自己的朋友的朋友通讯)19 //List
studentList = c.StudentList;20 //foreach (Student s in studentList)21 //{22 // Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);23 //}24 #endregion25 26 }27 }28

现在的关系是:一个学校有多个班级,每个班级有多个学生,现在学校想要管理学生,学校可以直接跟班级通讯,然后班级跟学生通讯,这就是所谓的只与直接朋友通讯,然后避免依赖更多类型(这个类型不包含:基类库BCL--框架内置)

其实类与类之间的关系可以总结为:

1:纵向:继承≈实现(最密切)

2:横向:聚合> 组合> 关联> 依赖(出现在方法内部)

依赖别人更少,也让别人了解更少,比如我们项目中经常中用到的一些访问修饰符:

Private:私有

Protected:子类才能获取到,子类才能访问到
Internal :当前dll才能看见
Public:公开得
Protected internal 叠加要么是子类, 要么是相同类库

 

其实项目中能体现迪米特法则的地方比如:三层架构(UI-BLL-DAL),还有我们习惯创建的中间层(UI-中间层--(调用不同的业务逻辑进行组合)),另外还有门面模式

 另外需要注意的是:单一职责法则只关注单类的功能;迪米特法则关注的是类与类之间的联系

 四:依赖倒置原则(Dependence Inversion Principle)

依赖倒置原则:高层模块不应该依赖于低层模块,二者应该通过抽象依赖

那何为高层何为底层,一般来说使用者为高层,被调用者为低层,具体还是举例说明,我们先创建一个手机:

 
View Code

接着我们创建一个学生,然后学生玩手机:

1 public class Student 2 { 3     public int Id { get; set; } 4     public string Name { get; set; } 5  6     ///  7     /// 依赖细节  高层就依赖了底层 8     ///  9     /// 10     public void PlayiPhone(iPhone phone)11     {12         Console.WriteLine("这里是{0}", this.Name);13         phone.Call();14         phone.Text();15     }16 17     public void PlayLumia(Lumia phone)18     {19         Console.WriteLine("这里是{0}", this.Name);20         phone.Call();21         phone.Text();22     }23 24     public void PlayHonor(Honor phone)25     {26         Console.WriteLine("这里是{0}", this.Name);27         phone.Call();28         phone.Text();29     }30 }

然后上面的学生就是高层,而手机就是低层,然后学生玩手机不应该直接直接写PlayiPhone,PlayLumia,PlayHonor 等,显然这是依赖的细节,如果这样写,以后增加一个手机,则要在学生类中增加了一个play方法,这样就会使学生类反复修改而不稳定,所以我们一般会定义一个基类为AbstractPhone,然后把各个型号通用的手机属性和功能都写在基类中,然后在学生中增加一个Play的方法如下:

1 public void Play(AbstractPhone phone)2 {3     Console.WriteLine("这里是{0}", this.Name);4     phone.Call();5     phone.Text();6 }

这样的话以后增加手机的话只要继承AbstractPhone,则在外部都可以直接调用。因为父类出现的地方都可以用子类代替。

然后有人还说这个可以直接使用泛型来写如下:

public void PlayT
(T phone) where T : AbstractPhone{ Console.WriteLine("这里是{0}", this.Name); phone.Call(); phone.Text();}

这个T使用基类来进行约束,其实就等同于用父类参数类型!

调用的方法如下:

1  { 2      iPhone phone = new iPhone(); 3      student.PlayiPhone(phone); 4      student.PlayT(phone); 5      student.Play(phone); 6  } 7  { 8      Lumia phone = new Lumia(); 9      student.PlayLumia(phone);10      student.PlayT(phone);11      student.Play(phone);12  }13  {14      Honor phone = new Honor();15      student.PlayHonor(phone);16      student.PlayT(phone);17      student.Play(phone);18  }

然后这样写的好处主要分为以下两点:

1 :一个方法满足不同类型的参数

2:还支持扩展,只要是实现了这个抽象,不用修改Student,稳定的同时又多了扩展

这就是所谓的面向抽象编程,但是面向抽象编程只适合于通用功能,如果你想要在子类中有特殊的操作,比如想要在iphone手机新增一个其它手机没有的方法A,然后play方法还传入AbstractPhone这个参数,这样是么有办法访问到A,这就是非通用的,就不应该面向抽象

那何为面向抽象,以及面向抽象的好处?

面向抽象就是:只要抽象不变,高层就不变!

面向抽象的好处:

面向对象语言开发,就是类与类之间进行交互,如果高层直接依赖低层的细节,细节是多变的,那么低层的变化就导致上层的变化;如果层数多了,底层的修改会直接水波效应传递到最上层,一点细微的改动都会导致整个系统从下往上的修改!

而面向抽象,如果高层和低层没有直接依赖,而是依赖于抽象,抽象一般是稳定的,那低层细节的变化扩展就不会影响到高层,这样就能支持层内部的横向扩展,不会影响其他地方,这样的程序架构就是稳定的

以后很多的设计模式都会跟抽象有关,比如我们经常说的控制反转IOC就是一个很好的例子!依赖倒置原则(理论基础)---IOC控制反转(实践封装)---DI依赖注入(实现IOC的手段)!

 

五: 接口隔离原则(Interface Segregation Principle)

接口隔离原则则是:客户端不应该依赖它不需要的接口, 一个类对另一个类的依赖应该建立在最小的接口上;

这个原则跟自己实际工作有很大的关系,总结下来可以通过以下几点来定义接口:

1: 既不能是大而全,会强迫实现没有的东西,也会依赖自己不需要的东西

2 :也不能一个方法一个接口,这样面向抽象也没有意义的
按照功能的密不可分来定义接口,
而且应该是动态的,随着业务发展会有变化的,但是在设计的时候,要留好提前量,避免抽象的变化
没有标准答案,随着业务和产品来调整的

3 :接口合并 Map--定位/搜索/导航 这种属于固定步骤,业务细节,尽量的内聚,在接口也不要暴露太多业务细节(就是密切相连的可以放在一个接口来实现,而不是分多个接口实现功能)

 

六:开闭原则(Open Closed Principle)

开闭原则:对扩展开发,对修改关闭

开闭原则只是一个目标,并没有任何的手段,也被称之为总则,其他5个原则的建议,就是为了更好的做到OCP, 开闭原则也是面向对象语言开发一个终极目标!

如果有功能增加/修改的需求可以通过优先级考虑以下修改:A:修改现有方法---B:增加方法---C:增加类---D:增加/替换类库

原作出自于:

转载于:https://www.cnblogs.com/Ramon-Zeng/p/10266452.html

你可能感兴趣的文章
oracle 报错ORA-12514: TNS:listener does not currently know of service requested in connec
查看>>
基于grunt构建的前端集成开发环境
查看>>
MySQL服务读取参数文件my.cnf的规律研究探索
查看>>
java string(转)
查看>>
__all__有趣的属性
查看>>
写博客
查看>>
利用循环播放dataurl的视频来防止锁屏:NoSleep.js
查看>>
python3 生成器与迭代器
查看>>
java编写提升性能的代码
查看>>
ios封装静态库技巧两则
查看>>
Educational Codeforces Round 46 (Rated for Div. 2)
查看>>
Abstract Factory Pattern
查看>>
C# 实现Bresenham算法(vs2010)
查看>>
基于iSCSI的SQL Server 2012群集测试(一)--SQL群集安装
查看>>
list 容器 排序函数.xml
查看>>
存储开头结尾使用begin tran,rollback tran作用?
查看>>
Activity启动过程中获取组件宽高的五种方式
查看>>
java导出Excel表格简单的方法
查看>>
SQLite数据库简介
查看>>
利用堆实现堆排序&优先队列
查看>>