六大原则
1. 单一职责原则(Single Responsibility Principle)
2. 里氏替换原则(Liskov Substitution Principle)
3. 迪米特法则 (Law Of Demeter)
4. 依赖倒置原则(Dependence Inversion Principle)
5. 接口隔离原则(Interface Segregation Principle)
6. 开闭原则 (Open Closed Principle)
这六大原则只是面向对象开发中推荐的一些指导建议,没有明确的套路,在有些场景下可能忽略甚至违背这些原则,这些建议其实是开发前辈们总结下来的,我们也算是站在前辈的肩膀上学习
单一职责原则(Single Responsibility Principle)
类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时, 有可能会导致原本运行正常的职责P2功能发生故障。 这就违背了单一职责。
一个类只负责一件事儿
一个方法只负责一件事儿
比如下面代码:
封装的动物类:
/// <summary> /// 封装 /// 动物类 /// 简单意味着稳定 /// </summary> public class Animal { private string _Name = null; public Animal(string name) { this._Name = name; } /// <summary> /// /// </summary> public void Breath() { if (this._Name.Equals("鸡")) { Console.WriteLine($"{this._Name}:呼吸空气!"); } else if (this._Name.Equals("鱼")) { Console.WriteLine($"{this._Name}:呼吸水!"); } } /// <summary> /// 在这个时候,我们就应该要考虑 拆分 /// </summary> public void Action() { if (this._Name.Equals("鸡")) { Console.WriteLine($"{this._Name}:Flying!"); } else if (this._Name.Equals("鱼")) { Console.WriteLine($"{this._Name}:Swimming!"); } } }
调用:
public class SRPShow { public static void Show() { { Animal animal = new Animal("鸡");//呼吸空气 animal.Breath(); animal.Action(); } { Animal animal = new Animal("鱼"); // 呼吸水 animal.Breath(); animal.Action(); } } }
从Breath和Action方法中看到有分支,现在可能只有2种动物,后期如果再加的就会很多,写了太多的分支判断,取执行各自的业务逻辑的时候,就容易出现职责不单一。
补充: 构造函数方法冒号后面加base()表示调用父类的构造方法
解决方法:可以抽象出一个基类,每个动物拆分出一个类继承基类的Breath和Action方法,就是说通过抽象继承实现了多态。这个的话每个类都只负责单独的一个动物的行为,和其他动物类互不干扰。
基类:
/// <summary> /// 抽象类 /// </summary> public abstract class AnimalAbsract { public string _Name = null; public AnimalAbsract(string name) { this._Name = name; } /// <summary> /// /// </summary> public abstract void Breath(); /// <summary> /// 在这个时候,我们就应该要考虑 拆分 /// </summary> public abstract void Action(); }
鸡:
public class Chicken:AnimalAbsract { /// <summary> /// /// </summary> public Chicken() : base("鸡") { } public override void Action() { Console.WriteLine($"{this._Name}:Flying!"); } public override void Breath() { Console.WriteLine($"{this._Name}:呼吸空气!"); } }
鱼:
public class Fish : AnimalAbsract { public Fish() : base("鱼") { } public override void Action() { Console.WriteLine($"{this._Name}:Swimming!"); } //如果某一种动物的动作改变了,这里只需要修改当前这类 public override void Breath() { Console.WriteLine($"{this._Name}:呼吸水!"); } }
调用形式:
AnimalAbsract animal = new Chicken(); animal.Breath(); animal.Action();
究竟在什么时候需要遵循这个单一职责呢?
如果方法够简单,类够少,其实违背一下这个单一职责也无所谓!
如果方法多了,业务逻辑负责,建议遵循单一职责!
方法的单一职责:一个方法只负责一件事儿,(根据职责分拆小方法,避免分支逻辑判断)
类的单一职责:一个类只负责一件事儿
类库的单一职责: 一个类库应该职责清晰,比如一个类库可能只负责创建模型,只负责通用方法等等。
系统层面的单一职责:为通用的功能分拆系统,
遵循单一职责的优点:
(1)降低类的复杂度,一个类只负责一项职责。
(2)提高类的可读性,可维护性
(3)降低变更引起的风险。
我们需要根据实际情况尽可能的根据单一职责原则来设计,但是不是说一定要按照这个原则。
里氏替换原则
定义:任何使用基类的地方,都可以透明的使用其子类
继承:子类拥有基类一切的属性和行为,任何基类出现的地方
规则
1 父类有的,子类是必须有的;
如果出现子类不应该有的东西,那就需要断掉继承; 或者可以再来一个父类,在这个父类里面只包含应该有的东西。
2 子类可以拥有自己的属性和行为
子类出现的地方, 父类不一定能够代替 ;我喜欢小动物,我一定喜欢狗狗, 如果我喜欢狗狗,却不一定喜欢小动物
3 父类实现的东西,子类就不要再写 (就是避免使用new 关键字隐藏,使用这个会找到此类的父类方法来执行)
使用了这个关键字以后可能会出现点意外 ,建议使用抽象类 或者虚方法 来进行修改父类的行为
4 父类已经实现的东西,子类要改的话,必须用virtural+override的形式重写
总之,里氏替换原则告诉了我们如何使用继承,尽量避免使用new。在实际代码中左边尽量写父类,这样更灵活。
为什么避免new?
因为普通的方法在编译的时候就知道这个方法属于哪个类,但是如果加了new,只能在运行时候才知道具体是谁的,比如下面代码:
public class Chinese { public string Kuaizi { get; set; } public void SayHi() { Console.WriteLine("早上好,吃了吗?"); } } public class Hubei : Chinese { public string Majiang { get; set; } public new void SayHi() //new 隐藏父类方法 { Console.WriteLine("早上好,过早了么?"); } }
调用:
Chinese hubei = new Hubei(); hubei.SayHi(); var hubei1 = new Hubei(); hubei1.SayHi();
结果:
早上好,吃了吗?
早上好,过早了么?
使用了new之后,使用第一种调用方法就会直接找父类的方法来执行,忽视掉当前类的方法
迪米特法则
迪米特法则(最少知道原则):一个对象应该对其他对象保持最少的了解。 只与直接的朋友通信。
(1)比如说一个系统往另一个系统传数据,不同的类型可能会有不同的数据结构,也会有不同的处理方法,我们不能让发送方来判断该给哪个方法传数据,接收方应该开放一个通用的接口,接收到数据之后自己做处理,不能让调用方来做接收方的业务逻辑。
类和类之间的关系,分为两个维度:
纵向:继承 实现(相似度几乎100%)
横向:依赖 (出现在方法中的其他对象)
比如说我要打印出这个学校下面的所有班级以及对应班级下的所有学生信息:
学校:
/// <summary> /// 学校 /// </summary> public class School { public int Id { get; set; } public string SchoolName { get; set; } public List<Class> ClassList { get; set; } public void Manage() { Console.WriteLine("Manage {0}", this.GetType().Name); foreach (Class c in this.ClassList) { Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName); List<Student> studentList = c.StudentList; foreach (Student s in studentList) { Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName); } } } }
调用:
public class LODShow { public static void Show() { Console.WriteLine("************************"); School school = new School() { SchoolName = "好学校", ClassList = new List<Class>() { new Class() { ClassName="一班", StudentList=new List<Student>() { new Student() { StudentName="学生1" }, new Student(){ Id=123, StudentName="学生2" }, new Student(){ Id=234, StudentName="学生3" } } } } }; school.Manage(); } }
这样写完全没问题,在学校类中直接处理班级信息没什么问题,但是在又直接遍历了学生信息,这相当于跨级干涉了,违反了迪米特法则最少知道原则,学校最直接的"朋友 "是班级,所以在学校类中可以只处理自己的信息然后让班级类来处理班级自己的信息,在班级类中处理自己的信息的同时也让学生类处理自己的信息:
完整代码:
/// <summary> /// 学校 /// </summary> public class School { public int Id { get; set; } public string SchoolName { get; set; } public List<Class> ClassList { get; set; } public void Manage() { Console.WriteLine("Manage {0}", this.GetType().Name); foreach (Class c in this.ClassList) { // Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName); c.ManageClass(); // List<Student> studentList = c.StudentList; // foreach (Student s in studentList) // { // Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName); // } } } } /// <summary> /// 班级 /// </summary> public class Class { public int Id { get; set; } public string ClassName { get; set; } public List<Student> StudentList { get; set; } public void ManageClass() { Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.ClassName); foreach (Student s in this.StudentList) { s.ManageStudent(); //Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName); } } } /// <summary> /// 学生 /// </summary> 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
private
protected 子类和自己访问
internal
protected internal 程序集内部和子类访问