大旗不挥,谁敢冲锋--6大设计原则

  • 单一职责原则

    • “你设计的类符合SRP原则吗?”--保准对方立马“萎缩”掉,而且还一脸崇拜的看着你,心想“老大确实英明!”。你可能会问了SRP是什么,别着急,往下看:
    • 之前常用的模型--RBAC(Role-Based Access Control)基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体与资源的行为分离

      • 类图

      • 这个类图设计的有问题,用户的属性和用户行为没有分开,这是一个严重的错误!应该把用户的信息抽取成一个BO(Business Object,业务对象),把行为抽取成一个Biz(Business Logic,业务逻辑),修正如下:
      • 依赖单一原则
        • 类图

    • Single Responsibility Principle(SRP):Three should never be more than one reason for a class to change.

    • 例:电话

      电话通话时有四个过程:1. 拨号2. 通话3. 回应4. 挂机
      
    • 这个接口有没有问题,IPhone这个接口可不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机。chat()实现的是数据传送。
      
    • 拆分后的类图:
    • 继续优化:
    • 单一职责原则的好处:
      1. 类的复杂度降低,实现什么职责都有清晰明确的定义
      2. 可读性提高,复杂性降低->可读性提高
      3. 可维护性提高,可读性提高->更易维护
      4. 变更引起的风险降低
      
    • 单一职责也适用于方法--一个方法尽可能的做一件事情
    • 最佳实践
      • The is sometimes hard to see.单一职责非常优秀,但是确实受非常多的因素制约,必须去考虑项目工期、成本、人员技术水平、硬件条件、网络情况,甚至是政策,垄断协议等因素。
      • 对于单一职责原则,建议是接口一定做到单一职责,类的设计尽量做到只有一个原因引起变化。
    • 小结
      Single Responsibility Principle (SRP)从职责(改变理由)的侧面上
      为我们对类(接口)的抽象的颗粒度建立了判断基准:
      在为系统设计类(接口)的时候应该保证它们的单一职责性。
      
  • 里式替换原则

    • 爱恨纠葛的父子关系

      • 继承优点

        • 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
        • 提高代码的重用性
        • 龙生龙,凤生凤,老鼠生来会打洞--种
        • 世界上没有两片完全相同的树叶--不同
        • 提高代码的可扩展性
        • 提高代码或项目的开放性
      • 继承缺点
        • 继承是入侵性的--只要继承,就必须拥有父类的可继承的属性和方法
        • 降低代码的灵活性
        • 增强了耦合性
    • 里式替换原则,让继承的“利”发挥最大作用,同时减少“弊”带来的麻烦。
      • if for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
      • “如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S是T的子类型”
      • “只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,但是反过来就不行了,有子类出现的地方,父类未必就能适应”
        • 里式替换原则为继承定义了一个规范

          • 子类必须完全实现父类的方法
          • 子类可以有自己的个性
          • 覆盖或实现父类的方法时输入的参数可以被放大(eg:HashMap->Map)
        • 最佳实践
          1. 尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了
          2. 把子类当成父类使用,子类的“个性”就被抹杀了
          3. 把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离
          
        • 小结

          里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。
          任何基类可以出现的地方,子类一定可以出现。
          LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,
          基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为
          
          里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
          
          在使用里氏代换原则时需要注意如下几个问题:(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。
          根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,
          如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。(2)  我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,
          并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,
          同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
          里氏代换原则是开闭原则的具体实现手段之一。(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,
          这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。
          
    例://父类--定义了eat()方法abstract class Super{public void eat();}//猫子类class CatSub extends Super{public void eat(){System.out.print("eat fish~~");}}//狗子类class DogSub extends Super{public void eat(){System.out.print("eat meat~~");}}//测试public class Test{public static void main (String[] args){Super s1=new CatSub();//CatSub s1=new Catsub();s1.eat();Super s2=new CatSub();//CatSub s2=new Catsub();s2.eat();}}
    
  • 依赖倒置原则

    • Dependence Inversion Principe(DIP)
    • High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
      1. 高层模块不应该依赖低层模块,两者都应该依赖抽象
      2. 抽象不应该依赖细节
      3. 细节应该依赖抽象
      
      在java中的表现1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系通过接口或抽象类产生的。2. 接口或抽象类不依赖实现类3. 实现类依赖接口或抽象类
      
    • 言而无信,你需要太多契约

      --论题:依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引发的风险,提高代码的可读性和可维护性
      --反论题:不使用依赖倒置也可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性
      --论据:汽车
      
      旧版:public class Dvriver{public void driver(Benz benz){benz.run();}}public class Benz{public void run(){System.out.println("奔驰车在跑!");}}public class Client{public static void main(String[] args){Driver d=new Driver();d.driver(new Benz());}}//要想开宝马车,还得把宝马车创建出来public class BMW{public void run(){System.out.println("宝马车在跑!");}}
      
      改进版://建立两个接口:IDriver和ICarpublic interface IDriver{public void driver(ICar car);}public interface ICar{public void run();}
      
    • 依赖的三种写法

      • 1.构造函数传递依赖对象

        public interface IDriver{public void driver();
        }public class Driver implements IDriver{private ICar car;public Driver(ICar car){this.car=car;}
        }
        
      • 2.Setter方式传递依赖对象

        public interface IDriver{public void setCar(ICar car);public void driver();
        }public class Driver implements IDriver{private ICar car;public void setCar(ICar car){this.car=car;}public void driver(){this.car.run();}
        }
        
      • 3.接口声明依赖对象
        • 在接口的方法中声明依赖对象

          public interface IDriver{public void setCar(ICar car);
          }
          
    • 最佳实践
      依赖倒置的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,互不影响,实现模块之间的松耦合,使用这个规则,遵循以下几个规则:1. 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备。2. 变量的表名类型尽量是接口或抽象类3. 任何类都不应该从具体类派生4. 尽量不要覆写基类的方法:基类如果是抽象类,而且方法已经实现,子类尽量不要覆写5. 结合里式替换原则
      
    • 到底什么是“依赖倒置”?
      依赖正置:类间的依赖是实实在在的类间依赖,也是面向实现编程。
      依赖倒置:对现实世界进行抽象,抽象的结果就是有了抽象类和接口,然后我们根据系统的需要就产生了抽象间的依赖,“倒置”就从这里产生。
      
  • 接口隔离原则

    • 接口分类(有点小小颠覆)

      • 实例接口--Person zhangsan=new Person();中Person就是zhangsan的接口。
      • 类接口--java中用interface关键字定义的接口
    • 隔离定义
      • Clients Should not be forced to depend upon interfaces that ther don't use.(客户端不应该依赖它不需要的接口)
      • The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)
    • 美女何其多,观点各不同

      选美女:1. 笼统(一个类中)1. 名字2. 脸蛋3. 气质4. 身材2. 隔离原则(拆分为两个接口)1. 外形美2. 气质棒
      
      例:interface I {  public void method1();  public void method2();  public void method3();  }  class I1 implements I{public void method1(){System.out.print("我只要方法1");} public void method2(){} public void method3(){} }  class I2 implements I{public void method1(){} public void method2(){System.out.print("我只要方法2");} public void method3(){} }class I3 implements I{public void method1(){} public void method2(){}public void method3(){System.out.print("我只要方法3");}  }上面的例子方法的实现中,只想用改用的方法,这里就需要接口隔离了:interface IM1 {  public void method1();  }  interface IM2 {  public void method2();  } interface IM3 {  public void method3();  } class I1 implements IM1{public void method1(){System.out.print("我只要方法1");} }  class I2 implements IM2{public void method2(){System.out.print("我只要方法2");} }class I3 implements IM3{public void method3(){System.out.print("我只要方法3");}  }
      
    • 保证接口的纯洁性

      1. 接口一定要小不能违反单一职责原则
      2. 接口要高内聚提高接口、类、模块的处理能力,减少对外的交互
      3. 定制服务单独为一个个体提供提供优良的服务,只提供访问者需要的方法,减少可能引起的风险
      4. 接口设计是有限度的接口设计粒度越小,系统越灵活,但要掌握好“度”
      
    • 最佳实践
      1. 一个接口只服务于一个子模块和业务逻辑
      2. 压缩接口中的public方法,做到“满身筋骨肉”
      3. 已经被污染了的接口,尽量去修改,若更改的风险大,则采用适配器模式进行转化处理
      4. 了解环境,拒绝盲从
      
  • 迪米特法则

    • Law of Demeter(Lod);Only talk to your immediate friends(只和朋友交流)
    • 也称最少知识原则(Least Knowledge Principe,LKP)
    • 通俗的讲:一个类应该对自已需要耦合或调用的类知道的最少,你的内部是如何复杂都和我没关系,那是你自己的。
    • 我的知识你知道的越少越好
    • 四层含义
      1. 只和直接朋友交流--不间接产生关系
      2. 朋友间也是有距离的--两只刺猬取暖,即可以相互取暖,又可以不伤害对方;两个关系太亲密,暴露的细节就多了,耦合关系变得异常牢固--即一个类的public属性或方法越多,修改涉及的范围就越大,因此,应该尽量减少public的属性和方法或者修改相应的权限和修饰符
      3. 是自己的就是自己的:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
      4. 谨慎使用Serializable:当属性的权限修改(假设扩大权限)后,服务器上没有做出相应的反变更,就会报序列化失败
      
    • 例子

      //将军类
      class JiangJun {//命令下属统计兵数public void commond(XiaShu xiashu){List<ShiBing> lists = new ArrayList() ;for(int i=0;i<20;i++){lists.add(new ShiBing());}xiashu.counts(lists);}
      }//下属类
      class XiaShu{public int counts(List<ShiBing> lists){return lists.size();}
      }//士兵类
      class ShiBing{...
      }//测试
      public class Test{public static void main(String[] args){JiangJun j=new JiaJun();j.commond(new XiaShu());}
      }*****上述的案例,将军类除了和下属直接联系,还添加了士兵,类间的耦合度骤然提高了,解决如下:
      //将军类
      class JiangJun {//命令下属统计兵数public void commond(XiaShu xiashu){xiashu.counts();}
      }//下属类
      class XiaShu{public int counts(){List<ShiBing> lists = new ArrayList() ;for(int i=0;i<20;i++){lists.add(new ShiBing());}return lists.size();}
      }//士兵类
      class ShiBing{...
      }//测试
      public class Test{public static void main(String[] args){JiangJun j=new JiaJun();j.commond(new XiaShu());}
      }
      
    • 最佳实践

      1. 迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了,类的复用率才可以提高,但是要求的结果就是产生了大量的中转或跳转类,导致系统的复杂度提高,同时也给维护带来了难度。需要反复权衡,既做到结构清晰,又做到高内聚,低耦合
      2. 类间跳转不超过两次是可以接受的
      
  • 开闭原则

    • 建立一个稳定的、灵活的系统
    • Software entities like classes,modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭)
    • 软件实体
      1. 项目或软件产品中按照一定逻辑规划划分的模块
      2. 抽象和类
      3. 方法
      
    • 一般解决办法
      • 修改接口
      • 修改实现类
      • 通过扩展实现变化
    • 变化的类型
      • 逻辑变化
      • 子模块变化
      • 可见视图变化
    • 例子

      //画动物类
      class PaintAnimal{public paint(String name){if("cat".equals(name)){PCat pcat=new PCat();pcat.paint();}else if("dog".equals(name)){PDog pdog=new PDog();pdog.paint();}}
      }//画猫类
      class PCat{public void paint(){System.out.print("^~^ 喵");}
      }//画狗类
      class PDog{public void paint(){System.out.print(":~: 汪汪");}
      }//测试类
      public class Test{public static void main (String[] args){PaintAnimal pa=new PaintAnimal();pa.paint("cat");//画猫}
      }*****以上代码中,根据String的传入来进行判断,首先这是很耗费时间的,需要一个一个判断,
      而且如果要新增画蛇,画马...这样改动代码就稍稍麻烦了,修改了源代码,而不是扩展,改进如下:
      //画动物类
      class PaintAnimal{public paint(PAnimal pa){pa.paint();}
      }//新增抽象画动物类
      abstract class PAnimal{public void paint();
      }//画猫类
      class PCat extends PAnimal{public void paint(){System.out.print("^~^ 喵");}
      }//画狗类
      class PDog extends PAnimal{public void paint(){System.out.print(":~: 汪汪");}
      }//测试类
      public class Test{public static void main (String[] args){PaintAnimal pa=new PaintAnimal();pa.paint(new PCat());//画猫}
      }
      
    • 开闭原则的重要性

      1. 开闭原则对测试的影响--变化产生时,原有的健壮代码是否可以不修改,只通过扩展来进行修改(单元测试中,Keep the bar green to keep the code clean,即保持绿条有助于代码整洁),新增加的类,新增加的测试方法,只要是正确的就行了
      2. 开闭原则可以提高代码的复用性
      3. 开闭原则可以提高代码的可维护性
      4. 面向对象的开发要求
      
    • 如何使用开闭原则
      1. 抽象约束1. 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public的方法2. 参数类型,引用对象尽量使用接口或抽象类,而不是实现类3. 抽象层尽量保持稳定,一旦确定即不允许修改
      2. 元数据(metadata)控制模块行为
      3. 制定项目章程
      4. 封装变化1. 将相同的变化封装到一个接口或抽象类中2. 将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中
      
    • 最佳实践
      1. 开闭原则也只是一个原则
      2. 项目章程非常重要
      3. 预知变化

总结

  • Single Responsibility Principle单一职责原则
  • Open Closed Principe开闭原则
  • Liskov Substitution Principe里式替换原则
  • Law of Demeter迪米特法则
  • Interface Segregation Principe接口隔离原则
  • Dependence Inversion Principe依赖倒置原则
  • 把上面的六个原则的英文的首字母拿出来拼一下,就是SOLID(solid,稳定的),其代表的含义也就是把这六个结合使用的好处:建立稳定、灵活、健壮的设计,而开闭原则又是重中之重、最基础的原则,是其他五大原则的精神领袖。

说明

  • 摘自秦小波《设计模式之禅》第2版;
  • 仅供学习,严禁商业用途;

设计模式之禅【六大设计原则】相关推荐

  1. “设计模式之禅”——六大设计原则详解解读

    目录 一.单一职责原则 二.里氏替换原则 三.依赖倒转原则 四.接口隔离原则 五.迪米特法则 六.开闭原则 一.单一职责原则 单一职责原则的英文名称是:Single Responsibility Pr ...

  2. 设计模式之禅(六大设计原则)

    1.单一职责原则(Single Responsibility Principle) 也就是职责划分要明确,单一职责原则提出了一个编写程序的标准,用"职责"或者"变化原因& ...

  3. 引用防删——JAVA设计模式总结之六大设计原则

    JAVA设计模式总结之六大设计原则 从今年的七月份开始学习设计模式到9月底,设计模式全部学完了,在学习期间,总共过了两篇:第一篇看完设计模式后,感觉只是脑子里面有印象但无法言语.于是决定在看一篇,到9 ...

  4. java设计模式总结之六大设计原则(有图有例子)

    转载:https://www.cnblogs.com/jpfss/p/9765239.html 下面来总结下自己所学习的设计模式,首先我们看下各个模式之间的关系图,下面这张图是网上比较典型的一个类图关 ...

  5. JAVA设计模式总结之六大设计原则(一)

    从今年的七月份开始学习设计模式到9月底,设计模式全部学完了,在学习期间,总共过了两篇:第一篇看完设计模式后,感觉只是脑子里面有印象但无法言语.于是决定在看一篇,到9月份第二篇设计模式总于看完了,这一篇 ...

  6. 设计模式系列,六大设计原则

    设计模式和性能优化有没有关系?最近,我看到有人再讲性能优化的时候,讲到了"有些设计模式可以做到一定程度的性能优化". 我读书少,别骗我.我看过无数篇设计模式了,第一次听到有人说,设 ...

  7. 举例说明层次分析的三大原则_设计模式系列,六大设计原则

    设计模式和性能优化有没有关系?最近,我看到有人再讲性能优化的时候,讲到了"有些设计模式可以做到一定程度的性能优化". 我读书少,别骗我.我看过无数篇设计模式了,第一次听到有人说,设 ...

  8. 设计模式必备知识点----六大设计原则

    六大设计原则 一,开闭原则 开闭原则的定义 什么是开闭原则 开闭原则的作用 开闭原则的优点 二,单一职责原则 单一职责定义 单一职责的作用 单一职责的优点 单一职责的违背原则 三,依赖倒置原则 依赖倒 ...

  9. Java 设计模式总结及六大设计原则

    设计模式总结 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式. ...

  10. Java设计模式中的六大设计原则

    最近一直在看有关设计模式的博客和文章,发现自己对于设计模式的认识和理解还是有点浅显,于是想动手写博客巩固一下. 在开始阐述设计模式之前,首先介绍一下设计模式中的六大原则:      总原则-开闭原则 ...

最新文章

  1. WMI技术介绍和应用——事件通知
  2. 设计模式的征途—10.装饰(Decorator)模式
  3. AndroidStudio SSL peer shut down incorrectly
  4. docker 镜像基本操作、镜像与容器常用指令
  5. scrum立会报告+燃尽图(第三周第七次)
  6. k8s1.5.4挂载volume之nfs
  7. ASP.NET简易教程-页面布局
  8. 极客大学产品经理训练营 产品思维和产品意识(中) 第4课总结
  9. 小米air2se耳机只有一边有声音怎么办_小米蓝牙耳机Air2 SE,169元是否值得购买?值...
  10. mac无法验证您网络上的打印机,怎么解决?
  11. 英语时态=时间+状态
  12. C++:实现量化daycounters 日计数器测试实例
  13. bat如何获取当前Windows系统的桌面路径
  14. Java实验:重写equals方法
  15. 论文中提到的池化方法的总结
  16. 《从零开始:机器学习的数学原理和算法实践》chap1
  17. linux apache ip配置,linux apache虚拟主机配置(基于ip,端口,域名)
  18. CentOS7搭建Hadoop集群环境
  19. 衢州计算机网络技术,衢州广播电视大学计算机网络技术专业_浙江报名_网络教育计算机网络技术专业教学计划_中国教育在线...
  20. XDOJ 378 正整数的最优分解

热门文章

  1. 安装perf后,执行perf命令报错。
  2. 【懒懒的Python学习笔记三】
  3. 男女人生良缘生克批意
  4. coso js 魔窗
  5. 深大uooc学术道德与学术规范教育第一章
  6. 【华为OD机试 2023最新 】 最短木板长度(C++ 100%)
  7. sql查询语句分支语句
  8. Netty第二章 2020 7-4——Netty在Dubbo中的应用(3)关于单一长连接
  9. Python random模块(获取随机数)常用方法和使用例子
  10. you-get下载优酷视频报错:用户账户异常、请重新登录