一、依赖倒置原则(Dependence Inversion Principle)
下面我们来看个示例:
////// 学生 /// public class Student { public int Id { get; set; } public string Name { get; set; } //依赖细节 高层就依赖了底层 public void PlayHonor(Honor phone) { Console.WriteLine("这里是{0}", this.Name); phone.Call(); phone.SendSMS(); } //依赖细节 高层就依赖了底层 public void PlayOppo(Oppo phone) { Console.WriteLine("这里是{0}", this.Name); phone.Call(); phone.SendSMS(); } } /// /// 荣耀手机 /// public class Honor { /// /// 打电话 /// public void Call() { Console.WriteLine("Use {0} Call", this.GetType().Name); } /// /// 发短信 /// public void SendSMS() { Console.WriteLine("Use {0} SendSMS", this.GetType().Name); } } /// /// Oppo手机 /// public class Oppo { /// /// 打电话 /// public void Call() { Console.WriteLine("Use {0} Call", this.GetType().Name); } /// /// 发短信 /// public void SendSMS() { Console.WriteLine("Use {0} SendSMS", this.GetType().Name); } }
在示例中Student类内部使用了Honor类和Oppo类,这里的使用者Student类就称为高层模块,被使用者Honor类和Oppo类就称为低层模块,这时候我们的高层模块就依赖于我们的低层模块。
那么这样子有什么不好呢?
1、如果此时我们的低层模块Honor类和Oppo类扩展了或者是发生变动了,则就有可能需要修改Student类,这就导致我们的Student类难以维护,Honor类和Oppo类扩展困难。
2、面向对象语言开发,就是类与类之间进行交互,如果高层直接依赖低层的细节,细节是多变的,那么低层的变化就导致上层的变化;如果层数多了,低层的修改会直接水波效应传递到最上层,一点细微的改动都会导致整个系统从下往上的修改。
那么我们怎么才能更好的解决这个问题呢?
竟然我们的低层模块是多变的,那么我们的高层模块就不应该依赖于低层模块,二者都应该依赖于抽象。
下面我们来改造一下:
////// 学生 /// public class Student { public int Id { get; set; } public string Name { get; set; } //依赖抽象 public void PlayHonor(AbstractPhone phone) { Console.WriteLine("这里是{0}", this.Name); phone.Call(); phone.SendSMS(); } //依赖抽象 public void PlayOppo(AbstractPhone phone) { Console.WriteLine("这里是{0}", this.Name); phone.Call(); phone.SendSMS(); } } /// /// 手机抽象类 /// public abstract class AbstractPhone { public int Id { get; set; } public string Branch { get; set; } /// /// 打电话 /// public abstract void Call(); /// /// 发短信 /// public abstract void SendSMS(); } /// /// 荣耀手机 /// public class Honor : AbstractPhone { /// /// 打电话 /// public override void Call() { Console.WriteLine("Use {0} Call", this.GetType().Name); } /// /// 发短信 /// public override void SendSMS() { Console.WriteLine("Use {0} SendSMS", this.GetType().Name); } } /// /// Oppo手机 /// public class Oppo : AbstractPhone { /// /// 打电话 /// public override void Call() { Console.WriteLine("Use {0} Call", this.GetType().Name); } /// /// 发短信 /// public override void SendSMS() { Console.WriteLine("Use {0} SendSMS", this.GetType().Name); } }
经过这样子改造之后,我们的高层模块Student就不直接依赖于低层模块了,而是通过抽象AbstractPhone类来依赖的。
这样子做有什么好处呢?
1、一个方法满足不同类型的参数。
2、还支持扩展,只要是实现了这个抽象,不用修改Student。
3、面向抽象,抽象一般是稳定的,那低层细节的变化或者是扩展就不会影响到高层,这样就能支持层内部的横向扩展,不会影响其他地方,这样的程序架构就是稳定的。
小结:
1、依赖倒置原则:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。应该依赖于抽象,而不是依赖细节。
2、抽象:接口/抽象类,可以包含没有实现的元素。细节:普通类,一切都是确定的。
3、面向抽象编程:尽量的使用抽象,80%的设计模式都是跟抽象有关。
4、面向抽象不止一个类型,用的就是通用功能;非通用的,那就不应该面向抽象。
5、面向抽象,只要抽象不变,高层就不变。
6、面向对象语言开发,就是类与类之间进行交互,如果高层直接依赖低层的细节,细节是多变的,那么低层的变化就导致上层的变化;如果层数多了,低层的修改会直接水波效应传递到最上层,一点细微的改动都会导致整个系统从下往上的修改。
7、面向抽象,抽象一般是稳定的,那低层细节的变化或者是扩展就不会影响到高层,这样就能支持层内部的横向扩展,不会影响其他地方,这样的程序架构就是稳定的。
8、依赖倒置原则(理论基础),IOC控制反转(实践封装),DI依赖注入(实现IOC的手段)。
二、接口隔离原则(Interface Segregation Principle)
接口隔离原则:客户端不应该依赖于它不需要的接口; 一个类对另一个类的依赖应该建立在最小的接口上。
怎么理解这个呢?下面我们来看个示例:
////// 手机抽象类 /// public abstract class AbstractPhone { public int Id { get; set; } public string Branch { get; set; } /// /// 打电话 /// public abstract void Call(); /// /// 发短信 /// public abstract void SendSMS(); /// /// 拍照 /// public abstract void Photo(); }
可以看出我们在上面的基础上加了一个拍照功能,我们都知道现在的智能手机都有拍照功能,但是呢老旧的诺基亚手机就没有拍照功能,所以说这样子写就是不合理的。
此时我们就需要对其进行拆分了:
////// 手机抽象类 /// public abstract class AbstractPhone { public int Id { get; set; } public string Branch { get; set; } /// /// 打电话 /// public abstract void Call(); /// /// 发短信 /// public abstract void SendSMS(); } /// /// 拍照接口 /// public interface IPhoto { /// /// 拍照 /// void Photo(); }
这样子改造后就符合一个类对另一个类的依赖建立在最小的接口上。
小结:
1、接口隔离原则:客户端不应该依赖于它不需要的接口; 一个类对另一个类的依赖应该建立在最小的接口上。
2、接口interface定义 can do 不局限产品。
3、接口到底该如何定义?
既不能是大而全,会强迫实现没有的东西,也会依赖自己不需要的东西。
也不能太细一个方法一个接口,这样面向抽象就没有意义了。
按照功能的密不可分来定义接口,而且应该是动态的,随着业务发展会有变化的,但是在设计的时候,要留好提前量,避免抽象的变化。
4、接口合并,业务细节要尽量的内聚,接口不要暴露太多业务细节(即接口不能太细了)。例如:
1、地图Map--定位/搜索/导航 ,这种属于固定步骤,就可以合并成一个接口。
2、手机--打电话/发短信,只要是手机这2个功能就都会有,那么我们就应该合并成一个接口。
三、开放封闭原则(Open Closed Principle)
小结:
1、开放封闭原则:对扩展开放,对修改封闭。
修改:修改现有代码(类)(例如:程序分支。)
扩展:增加代码(类)(例如:之前提到的单一职责原则。)
2、面向对象语言是一种静态语言,最害怕变化,会波及很多东西而导致全面测试。最理想就是新增类,对原有代码没有改动,原有的代码才是可信的。
3、其他5个原则的建议,就是为了更好的做到OCP,开闭原则也是面向对象语言开发一个终极目标。
4、如果有功能增加/修改的需求:修改现有方法(最不理想的)---增加方法(相对较好的)---增加类(比较理想的)---增加/替换类库(最理想的)。