设计模式六大原则

/ 技术 / 无站内评论 / 163浏览

每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心,这样,你就能一次又一次地使用该方案而不必做重复劳动。这个思想同样适用于面向对象的设计模式,核心就在于提供给了相关问题的解决方案

学习设计模式, 就是为了让我们显着更加专业, 是的, 我们是专业coder😂

希望童鞋们,多看几遍,直至“懂”,这个“懂”怎么定义?了解概念就算懂了吗?

我认为,真正使用设计模式,设计原则到实际项目业务中,解决或者预见性的设计一些流程。给以后的代码,流程提高可读性,可扩展、可维护性才是懂。

当然,理解概念后,需要自己去学习优秀项目中真正应用到的地方。学习理解为什么别人这么用,如果是自己会怎么去设计?多多思考。

每个公司都不缺只会写业务代码的程序员。努力成为别人无法代替的程序员。

设计模式的六大原则

https://www.cnblogs.com/denghailei/p/6214604.html

①单一原则

    意思: 只做一件事.

    我用个伪代码表达一下

 //计算文件中的
 class Calculator {
    //相加  
   public int add() throws NumberFormatException, IOException{
         File file = new File("E:/data.txt");
         BufferedReader br = new BufferedReader(new FileReader(file));
         int a = Integer.valueOf(br.readLine());
         int b = Integer.valueOf(br.readLine());
         return a+b;
    }
  }

    要是谁封装了这么一个类, 然后你用了, 卧槽什么怎么只有加法, 没有减法 乘法, 你然后是不是要 copy 一下 ,把+ 号改为其他符号 .

    q:这个类中哪里违反了单一原则呢?

    梳理一下, 我们用这个计算类是为什么干什么, 计算文本中的和

    引申一下, 实际的操作步骤:

                 ① 获取文件内容

                 ② 计算返回值        

    这个类同时拥有两个职责

    q:这个类还存在什么问题?

    在开发初期, 我们很有可能为了快速, 进行大量的copy, 这样就导致了很多的代码重复, 这样可能我们为了书写一个相减或相乘的方法, 来copy 加法中的代码进行修改

  下面我们需要把上面的代码修改一下

 //获取文件内容类
 class Reader{
 
     int a,b;
 
     public Reader(String path)throws Exception{
     
     BufferedReader br = new BufferedReader(new FileReader(new File(path)));
       a= Integer.valueOf(br.readLine);          
       b= Integer.valueOf(br.readLine);          
    }
     
     int getA(){
         return a;
    }
 
         
     int getB(){
         return b;
    }  
 }
 
 //单独的计算类
 
 class Calculator{
     
     int add(int , int b){
     return a+b;
 }    
 
     int subtract(int , int b){
     return a-b;
 }        
 }

优点:

增加代码的可读性,可维护性。

想象一下一个场景,一个方法中有N个功能代码堆积在一块。

后期如果修改功能,不说别人,就算自己开发的,也要从头开始阅读所有功能代码再找到要修改的地方。

如果使用单一原则就方便多了,一个功能块一个方法。

可读性大大提升,一眼过去即可找到要修改的功能模块。

ps:

其实我们项目中存在着大量类似代码,造成这种原因有很多种。我大体举例了几类

1.写代码随心所欲,拿到文档就开始写。

这种占比应该是最多的,不可否认,我以前也存在这样的问题。

往往在拿到需求文档后,第一时间开始写代码,所有代码堆积在一个类中,往往这是新手很喜欢犯的一个错误。

推荐大家拿到文档后,首先阅读别人类似功能的代码,思考有没有借鉴的地方(不是copy恶心代码),如果脑子第一反应是觉得代码恶心,阅读性差的话,好了不用copy了,自己设计一套优雅的流程,如果你继续copy,后面的童鞋继续copy你的.......N版迭代后,项目终有一天会无法维护。

别人在看到你的代码时,说出口往往是“什么垃圾代码”,而不是“卧槽,代码真骚啊。可以借鉴”。


②里氏替换原则

LSP的原定义比较复杂,我们一般对里氏替换原则 LSP的解释为:子类对象能够替换父类对象,而程序逻辑不变。

里氏替换原则有至少以下两种含义:

  1. 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义 。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。

  1. 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。

不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。

如何符合LSP?总结一句话 —— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。

通俗点:子类拿来就可以当父类用,而且还不出错。接口要符合,限制要比父类宽泛。

优点:

代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性 提高代码的重用性 子类可以形似父类,但是又异于父类。 提高代码的可扩展性,实现父类的方法就可以了。许多开源框架的扩展接口都是通过继承父类来完成。 提高产品或项目的开放性

但是所有的事物都有二面性,继承除了有上述优点,也有下面缺点:

继承是侵入性的,只要继承,就必须拥有父类的所有方法和属性 降低了代码的灵活性,子类必须拥有父类的属性和方法,让子类有了一些约束

增加了耦合性,当父类的常量,变量和方法被修改了,需要考虑子类的修改,这种修改可能带来非常糟糕的结果,要重构大量的代码。


③接口隔离原则

开发中中 我们经常发现 我们在实现一个接口的同时, 有很多方法都是空的

 //手机接口
 public interface BaseMobile {`
 
    public void call();//打电话
 
    public void sendSms();//发短信
 
    public void playBird();//玩游戏  
 
 }

只要是手机那就应该有打电话发短信的功能, 但是玩游戏的功能并不一定是每个手机都有的

所以我们可以先让去掉 玩游戏 这个接口 最小化这个接口

 //修改后的代码
 public interface BaseMobile {
 
  public void call();
 
  public void sendSms();  
 
 }
 
 //适配器
 public interface IOSMobile extends BaseMobile{
 
  public void playBird();
 
 }

    这样, 就很清晰的理解接口最小化原则,

     但是凡事都有特例,

     不需要发短信功能的手机存在吗, 很显然存在啊. 同样的, 时代发展的很快, 手机的基本功能也在变迁, 就像这样是个手机应该就能上网之类的功能

     ps:写到这里, 我明显感觉我的思考问题的方式在发生着改变, 我们的代码, 应该遵循的一些规范, 往往在人们的生活方式改变的同时改变着, 代码离不开生活, 美好的生活由代码构成, 在今后的社会, 有可能我们的代码就像是科学一样, 人人都应该了解她, 学习她.


④依赖倒置原则   

原则:面向接口编程。抽象指的是接口或抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约, 而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

https://www.cnblogs.com/chenxkang/p/6657744.html

以上文章讲的非常好,这里就不重复赘述了。

//正确用法

 //抽象 人
 public interface People{
 
 void myInfo();//自我介绍
 void canDo();//能做什么
 }
 
 //中国人
 public class China:People{
 public void myInfo(){
 System.out.println("我是中国人");  
 }
 void canDo(){
 System.out.println("我会code,搬砖,修车,开车,滑稽");  
 }
 }  
 
 //美国人
 public class America:People{
 public void myInfo(){
 System.out.println("我是美国人");  
 }
 void canDo(){
 System.out.println("我会code,mygod,yea,aye,huhuh");  
 }
 }  
 //高层
 public class PeopleInfo{
 
 private People people;
 
 public PeopleInfo(People people){
 this.people =people;
 }
 public void myInfo(){
 people.myInfo();
 }
 void canDo(){
 people.canDo();
 }
 
 }
 

思考,如果这里的高层PeopleInfo被定义成了某个具体的实现(中国人)

`

//反面例子

//高层

 public class PeopleInfo{
 
 private China china ;
 
 public PeopleInfo(China china ){
 this.china =china ;
 }
 public void myInfo(){
 china .myInfo();
 }
 void canDo(){
 china .canDo();
 }`

可能暂时实现了业务,如果以后扩展,该高层移民,变成了美国人?岂不是重新修改PeopleInfo的代码?将其修改成America,如果在实际项目很复杂的场景,岂不是要重写?

因此PeopleInfo这个类依赖于接口People, 而具体方法不会影响PeopleInfo , 只要改变实现People即可 ,也就是细节依赖于抽象

  并且 PeopleInfo 不依赖于China和America, 也就是依赖关系被倒置了

  这里我解释下可能有的同学不是很清楚其中的利害 依赖倒置

  PeopleInfo 依赖于 People ,当我们需要使用PeopleInfo做某些事情的时候, 这时需要注入China或其他实现类, 但是PeopleInfo并不依赖China,

PeopleInfo只依赖People .


⑤迪米特原则

  迪米特法则(Law of Demeter )又叫做最少知识原则,也就是说,一个对象应当对其他对象尽可能少的了解。不和陌生人说话。英文简写为: LoD。

一切都是为了高内聚低耦合的封装思想

有点难理解,我也是看了非常做资料之后才有一点感悟。

https://blog.csdn.net/liuziteng0228/article/details/54845132

https://blog.csdn.net/macrohui29/article/details/83627614

https://www.cnblogs.com/xiaobai1226/p/8670245.html

https://www.cnblogs.com/junyuhuang/p/5630539.html

一个类公开的public方法和属性越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private,package-private,protected等访问权限,是否可以加上final关键字。

注意: 迪米特原则要求类“羞涩”一点,尽量不要对外公开太多的public方法和非静态的public变量,尽量内敛,多使用private,package-private,protected等访问权限。

⑥开闭原则

定义:一个软件实体。如类/模块/函都应该对扩展开放,对修改关闭。

问题由来:在软件的生命周期内,因为变化,升级和维护等原因需要对软件原有代码进行修改,可能会给旧代码引入错误,也有可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现。


  开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统,开闭原则只定义了对修改关闭,对扩展开放。其实只要遵循前面5中设计模式,以及使用23种设计模式的目的就是遵循开闭原则,设计出来的软件就是符合开闭原则的。

  用抽象构建架构,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保证架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了,当然前提是抽象要合理,要对需求的变更有前瞻性和预见性。

那么如何去遵守这六个原则,对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度达到多少,任何事过犹不及,设计模式六个设计原则也是一样,制定这六个原则并不是一味的要求我们去遵守他们,而是根据实际情况灵活运用,

图中的每一条维度代表一项原则,我们依据对这个遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点会落在红色的圈中,如果遵守的差会落在小圆圈里,遵守的差会落在大圆圈外面,一个良好的设计体现在图中,应该是六个点都在同心圆中的六边形。

如上图,设计1和设计2属于良好设计,他们对六项原则遵守程度都在合理范围内,设计3和设计4虽然有些不足,但也基本可以接受,设计5和设计6,一个几乎不遵守,一个遵守过度,那么这两种设计模式都是迫切需要重构的。

原文https://blog.csdn.net/zhengzhb/article/details/7296944

有哪讲的不对或者不详细遗漏的,欢迎补充留言。

召唤蕾姆
琼ICP备18000156号

鄂公网安备 42011502000211号