经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
设计模式六大原则
来源:cnblogs  作者:冰乐  时间:2019/9/19 9:14:22  对本文有异议

设计模式六大原则:

  1、单一职责原则(Single Responsibility Principle)

  2、历史替换原则(Liskov Substitution Principle)

  3、依赖倒置原则(Dependence Inversion Principle)

  4、接口隔离原则(Interface Segregation Principle)

  5、迪米特法则(Law Of Demeter)//最少知道原则

  6、开闭原则(Open Closed Principle)

设计模式:

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

设计模式六大原则:

  面向对象语言开发过程中,推荐的一些指导性原则;没有明确的招数,而且也会经常被忽视/违背;也是前辈总结,也是为了站在前辈的肩膀上。

  实际上真实项目很难全部遵循,更多的时候会有一些侧重性。设计模式六大原则要能灵活运用,离不开时间的锤炼和思考,把这个真的融入到骨子里,设计确实会不一样。

1、单一职责(Single Responsibility Principle)

  类T负责两个不同的职责,职责P1和职责P2。由于职责P1需求发生改变需要修改T时,有可能会导致原本运行正常 职责P2功能发生故障。

  一个类只负责一件事,面向对象语言开发,类是一个基本单位,单一职责原则就是封装的粒度。

下面我们来看一个动物类:

  1. public class Animal
  2. {
  3. private string _Name = null;
  4. public Animal(string name)
  5. {
  6. this._Name = name;
  7. }
  8. /// <summary>
  9. /// 这个方法就挺不稳定,经常各种分支变化经常修改
  10. /// </summary>
  11. public void Breath()
  12. {
  13. if (this._Name.Equals(""))
  14. Console.WriteLine($"{this._Name} 呼吸空气");
  15. else if (this._Name.Equals(""))
  16. Console.WriteLine($"{this._Name} 呼吸空气");
  17. else if (this._Name.Equals(""))
  18. Console.WriteLine($"{this._Name} 呼吸水");
  19. else if (this._Name.Equals("蚯蚓"))
  20. Console.WriteLine($"{this._Name} 呼吸泥土");
  21. }
  22. //BreathChicken BreathFish
  23. //应该拆分了
  24. public void Action()
  25. {
  26. if (this._Name.Equals(""))
  27. Console.WriteLine($"{this._Name} flying");
  28. else if (this._Name.Equals(""))
  29. Console.WriteLine($"{this._Name} walking");
  30. else if (this._Name.Equals(""))
  31. Console.WriteLine($"{this._Name} Swimming");
  32. else if (this._Name.Equals("蚯蚓"))
  33. Console.WriteLine($"{this._Name} Crawling");
  34. }
  35. }

这样在不同动物过来时,只要在Animal的构造函数中传递动物的名字即可:

  1. {
  2. Animal animal = new Animal("");//呼吸空气
  3. animal.Breath();
  4. animal.Action();
  5. }
  6. {
  7. Animal animal = new Animal("");//呼吸空气
  8. animal.Breath();
  9. animal.Action();
  10. }
  11. {
  12. Animal animal = new Animal("");//呼吸水
  13. animal.Breath();
  14. animal.Action();
  15. }

  但是这样不好,写了分支判断,然后执行不同的逻辑,其实这就违背了单一职责,但是功能是可以实现的。

  需要拆分,拆分父类+子类,每个类很简单,意味着稳定,意味着强大。现在的东西没有以前警用,因为功能多了,这不坏就那坏了。

下面抽象出一个父类:

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

现在,有一种动物,只要继承这个父类,重写抽象类中的方法即可:

  1. public class Chicken : AbstractAnimal
  2. {
  3. public Chicken(string name) : base(name)
  4. {
  5. }
  6. public Chicken() : base("")
  7. {
  8. }
  9. public override void Breath()
  10. {
  11. Console.WriteLine($"{base._Name} 呼吸空气");
  12. }
  13. public override void Action()
  14. {
  15. Console.WriteLine($"{base._Name} flying");
  16. }
  17. }
  1. AbstractAnimal animal = new Chicken();
  2. animal.Breath();
  3. animal.Action();

拆分之后,也会造成代码量的增加,类多了,使用成本(理解成本)也高了。

那么,什么时候使用单一职责呢?

  如果类型足够简单,方法够少,是可以在类级别去违背单一职责的;如果类型复杂了,方法多了,建议遵循单一职责原则。

方法级别的单一职责原则:一个方法只负责一件事

类级别的单一职责原则:一个类只负责一件事

类库级别的单一职责原则:一个类库职责要清晰

项目级别的单一职责:一个项目应该职责要清晰(客户端/管理后台/后台服务/定时任务/分布式引擎)

系统级别的单一职责:为通用功能拆分系统(IP定位/日志/在线统计)

2、里氏替换原则(Liskov Substitution Principle)

  任何使用基类的地方,都可以透明(安全,不会出现行为不一致)的使用其子类

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

  a、父类有的,子类必须有,如果父类中出现了子类不应该有的东西,那么就应该断掉继承,再来一个父类,包含了都有的东西。

  b、子类可以有自己的属性和行为,子类出现的地方,父类不一定能代替(白马非马)。

  c、父类实现的东西,子类就不要再写了(就是不要new隐藏),有时候会出现意想不到的地方,行为不一致,如果想修改父类的行为,通过abstract或者virtual。声明属性、字段、变量,尽量声明为父类。

3、依赖倒置原则(Dependence Inversion Principle)

  高层模块不应该依赖于低层模块,二者应该是通过抽象依赖,依赖抽象,而不是依赖细节。面向对象,尽量使用抽象,80%的设计模式都是跟抽象有关(接口也可以)。属性、字段、方法参数、返回值。。。。尽量都是抽象

  抽象:接口/抽象类---可以包含没有实现的元素

  细节:普通类(子类)---一切都是确定的

  Student-------使用--------手机

  高层--------------------------低层

就好像三层中:

  BLL---------------------------DAL

如果在一开始,每个手机都定义通用的方法,学生在调用的时候,正常去调用,会出现高层依赖低层

 

  1. //Student-使用-手机
  2. //高层---------底层
  3. {
  4. iPhone phone = new iPhone();
  5. student.PlayiPhone(phone);
  6. student.PlayT(phone);
  7. student.Play(phone);
  8. }
  9. {
  10. Lumia phone = new Lumia();
  11. student.PlayLumia(phone);
  12. student.PlayT(phone);
  13. student.Play(phone);
  14. }
  15. {
  16. Honor phone = new Honor();
  17. student.PlayHonor(phone);
  18. student.PlayT(phone);
  19. student.Play(phone);
  20. }

 

解决上面出现的问题,定义一个手机的抽象父类,里面定义手机通用的功能。

  1. /// <summary>
  2. /// 定义一个手机抽象父类
  3. /// </summary>
  4.  
  5. public abstract class AbstractPhone
  6. {
  7. public int Id { get; set; }
  8. public string Branch { get; set; }
  9. public abstract void Call();
  10. public abstract void Text();
  11. }
  1. /// <summary>
  2. /// 荣耀手机
  3. /// </summary>
  4. public class Honor : AbstractPhone
  5. {
  6. public override void Call()
  7. {
  8. Console.WriteLine("User {0} Call", this.GetType().Name);
  9. }
  10. public override void Text()
  11. {
  12. Console.WriteLine("User {0} Call", this.GetType().Name);
  13. }
  14. }
  1. /// <summary>
  2. /// iPhone
  3. /// </summary>
  4. public class iPhone : AbstractPhone
  5. {
  6. public override void Call()
  7. {
  8. Console.WriteLine("User {0} Call", this.GetType().Name);
  9. }
  10. public override void Text()
  11. {
  12. Console.WriteLine("User {0} Call", this.GetType().Name);
  13. }
  14. }
  1. /// <summary>
  2. /// 小米手机
  3. /// </summary>
  4. public class Mi : AbstractPhone
  5. {
  6. public override void Call()
  7. {
  8. Console.WriteLine("User {0} Call", this.GetType().Name);
  9. }
  10. public override void Text()
  11. {
  12. Console.WriteLine("User {0} Text", this.GetType().Name);
  13. }
  14. public void Bracelet()
  15. {
  16. Console.WriteLine("User {0} Bracelet", this.GetType().Name);
  17. }
  18. }

......只要是一部手机,只要继承了抽象父类,都可以重写父类中的抽象方法,实现通用的功能,也可以加上自己特有的功能(增加自己的功能,是不是很强大?后面就看出来了是否合理了)。

下面有两种方式去调用:泛型+父类约束=========》用抽象类,是等价的

  1. public void PlayT<T>(T phone) where T : AbstractPhone
  2. {
  3. Console.WriteLine("这里是{0}", this.Name);
  4. phone.Call();
  5. phone.Text();
  6. }
  1. public void Play(AbstractPhone phone)
  2. {
  3. Console.WriteLine("这里是{0}", this.Name);
  4. phone.Call();
  5. phone.Text();
  6. //phone.Bracelet();
  7. }

面向抽象有啥好处?

  一个方法既满足不同类型的参数,只要是实现了这个抽象类,都可以用;还支持扩展,不用修改Student类;面向抽象后,不能使用子类特别的内容。

  在调用的时候,studen.Play(phone);如果,这个phone是一个小Mi手机的子类呢?Bracelet(手环功能)是有的,但是方法不能用,编译器决定了是不能用Bracelet(dynamic/反射是可以调用的),不能常规调用,这个问题是解决不了的。

  你这样想,如果小米手机都是自己特有的功能,那还用抽象父类干嘛?直接小米手机自己搞一个不就行了?因为面向对象不止一个类型,用的就是通用功能;非通用功能,那就不应该面向对象了。

  面向对象语言开发,只要抽象不变,高层就不变。

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

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

  依赖倒置原则(理论基础)------IOC控制反转(实践封装)-----DI依赖注入(实现IOC的手段)。

4、接口隔离原则(Interface Segregation Principle)

  客户端不应该依赖它不需要的接口,一个类对另外一个类的依赖应该建立在最小的接口上,在工作中,80%都是用接口的。

  AbstractPhone定义了Id、Branch、Call()、Text(),现在的智能手机,都有map、movie、online、game等功能,是否应该把这几个功能上升到AbstractPhone里面去?

  答案是不应该的,OldManPhone也是手机,但是没有这些功能,AbstractPhone就只能放入任何手机都有的功能。

  虽然这些东西不适合放在抽象类中,但是面向抽象编程,还有一个接口。抽象类  is  a,接口  can  do,面向对象编程中,是单继承多实现的。将这些东西定义在接口中,不局限产品。

  接口是什么?  

    简单来说,接口就是,当一些东西都有相同的功能,但是有不能抽象出合理的父类的时候,这个时候就可以抽象为接口。

  1. public interface IExtend
  2. {
  3. void Photo();
  4. void Online();
  5. void Game();
  6. void Record();
  7. void Movie();
  8. void Map();//
  9. void Pay();
  10. }//都拆成一个方法一个接口

 

  Camera能拍照,也能录像,既然面向抽象,那么有些功能的对象都能传递进来,那就让Camera也去实现IExtend接口?

  不可以的,实现了IExtend接口,Camera出现了很多自己没有的功能,不应该使用这种大而全的接口,所以要把接口的功能进一步拆分,因为接口是可以多实现的。

  1. public interface IExtend
  2. {
  3. //void Photo();
  4. //void Online();
  5. //void Game();
  6. void Record();
  7. //void Movie();
  8. void Map();//
  9. void Pay();
  10. }//都拆成一个方法一个接口
  11. //电视--上网 玩游戏
  12. public interface IExtendHappy : IExtendGame
  13. {
  14. void Online();
  15. //void Game();
  16. }
  17. //掌中游戏机:俄罗斯方块--玩游戏不能上网
  18.  
  19. public interface IExtendGame
  20. {
  21. void Game();
  22. }
  23. public interface IExtendVideo
  24. {
  25. void Photo();
  26. void Movie();//打开相机--切换模式--start--Suspend--End
  27. }

这样拆分下去,都拆成一个方法一个接口了,肯定也不好。

在.Net中,有IList<T>,是索引相关;ICollection<T>,集合相关操作;IEnumerable<T>,迭代器foreach....

接口到底该怎么定义?

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

  b、也不能一个方法一个接口,这样,面向抽象也没意义了。

    按照功能的密不可分可定义接口,而且应该是动态的,随着业务发展会有变化的,但是在设计的时候,要留好提前量,避免抽象变化。这里没有标准答案,随着业务来调整的。

  c、接口合并,Map----定位/导航/搜索,这种就属于固定步骤,业务细节,尽量的内聚,在接口也不要暴露太多细节。

5、迪米特法则(Law Of Demeter)最少知道原则:高内聚低耦合

  一个对象应该对其他对象保持最少的了解,只与直接的朋友通信。

  面向对象----万物皆对象-----类与类交互才能产生功能,这不就是耦合了吗?要高内聚低耦合

  类与类之间的关系:

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

    横向:聚合>组合>包含>依赖(出现在方法内部)

  迪米特法则:降低类与类之间的耦合,只与直接的朋友通信,就是要尽量避免依赖更多类型。

  基类库(BCL-系统/框架内置)的类型除外

  迪米特,也会增加一些成本

  工作中,会去早一个中介(中间层)

  上层UI下订单-----订单系统&支付&支付系统&仓储&物流

  门面模式----上层交互门面----门面依赖子系统

  三层架构中:UI----BLL-----DAL

  去掉内部依赖

  降低访问修饰符权限,private、protected、internal、protected internal、public

  迪米特,依赖别人更少,让别人了解更少

  1. /// <summary>
  2. /// 学生
  3. /// </summary>
  4. public class Student
  5. {
  6. public int Id { get; set; }
  7. public string StudentName { get; set; }
  8. public int Height { private get; set; }
  9. public int Salay;
  10. public void ManageStudent()
  11. {
  12. Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.StudentName);
  13. }
  14. }
  1. /// <summary>
  2. /// 班级
  3. /// </summary>
  4. public class Class
  5. {
  6. public int Id { get; set; }
  7. public string ClassName { get; set; }
  8. public List<Student> StudentList { get; set; }
  9. public void ManageClass()
  10. {
  11. Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.ClassName);
  12. foreach (Student s in this.StudentList)
  13. {
  14. s.ManageStudent();
  15. //Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
  16. }
  17. }
  18. }
  1.  

 

  1.  
  1. /// 学校
  2. /// </summary>
  3. public class School
  4. {
  5. public int Id { get; set; }
  6. public string SchoolName { get; set; }
  7. public List<Class> ClassList { get; set; }
  8. public void Manage()
  9. {
  10. Console.WriteLine("Manage {0}", this.GetType().Name);
  11. foreach (Class c in this.ClassList)
  12. {
  13. //Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName);
  14. c.ManageClass();//1 遵循了迪米特
  15. //List<Student> studentList = c.StudentList;
  16. //foreach (Student s in studentList)
  17. //{
  18. // Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
  19. //}//2 违背了迪米特法则
  20. }
  21. }
  22. }

 

6、开闭原则(Open Closed Principle)

  对扩展开放,对修改关闭

  修改:修改现有代码(类)

  扩展:增加代码(类)

  面向对象语言是一种静态语言,最害怕变化,会波及很多东西,全面测试。嘴里将就是新增类,对原有代码没有改动,原有的代码才是可信的。

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

  如果有功能增加/修改的需求,那么就修改现有的方法---增加方法---增加类----增加/替换类库。

 

  

 

 

 

 

 

 

 

 

 

 

原文链接:http://www.cnblogs.com/taotaozhuanyong/p/11545323.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号