设计模式七大原则:

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒置原则
  4. 里氏替换原则
  5. 开闭原则
  6. 迪米特法原则
  7. 合成复用原则

  8. 单一职责原则:

    对类来说,一个类应该只负责一项职责。但如果类A负责两个不同职责,则当职责1需求变更而改变A类,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2。
    image.png

  9. 接口隔离原则

一个类对另一个类的依赖应该建立在最小接口上。
image.png
从图中可以看出,类 A 依赖于 接口 I 中的方法 1,2,3 ,类 B 是对类 A 的具体实现。类 C 依赖接口 I 中的方法 1,4,5,类 D 是对类 C 的具体实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。

如果想符合接口隔离原则,就必须对接口 I 如下图进行拆分:
image.png

  1. 依赖倒装原则

依赖倒转的中心思想是面向接口编程。
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类。
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
image.png

  1. 里氏替换原则
  • 里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
  • 里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。
  • 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

    含义:
    1、子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法(重写)
    2、子类中可以增加自己特有的方法
    3、当子类重载父类的方法时,至少子类方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

正确示范:

  1. public class A {
  2. public void fun(HashMap map){
  3. System.out.println("父类被执行...");
  4. }
  5. }
  1. public class B extends A{
  2. public void fun(Map map){
  3. System.out.println("子类被执行...");
  4. }
  5. }
  1. public class demo {
  2. public static void main(String[] args){
  3. System.out.print("父类的运行结果:");
  4. A a=new A();
  5. HashMap map=new HashMap();
  6. a.fun(map);
  7. //父类存在的地方,都可以用子类替代
  8. //子类B替代父类A
  9. System.out.print("子类替代父类后的运行结果:");
  10. A b=new B(); //引用父类的地方可以使用子类生成的对象
  11. b.fun(map);
  12. }
  13. }

结果:image.png
子类方法的参数Map比父类方法的参数HashMap的范围要大,所以当参数输入为HashMap类型时,只会执行父类的方法,不会执行父类的重载方法。这符合里氏替换原则。

4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

  1. public class LSP1 {
  2. abstract class A {
  3. public abstract Map fun();
  4. }
  5. class B extends A{
  6. @Override
  7. public HashMap fun(){
  8. HashMap b=new HashMap();
  9. b.put("b","子类被执行...");
  10. return b;
  11. }
  12. }
  13. public static void main(String[] args){
  14. LSP1 lsp =new LSP1();
  15. LSP1.A a=lsp.new B();
  16. System.out.println(a.fun());
  17. }
  18. }

若在继承时,子类的方法返回值类型范围比父类的方法返回值类型范围大,在子类重写该方法时编译器会报错。
image.png

总结:
1、我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
2、在系统设计时,遵循里氏替换原则,尽量避免子类重写父类的方法,可以有效降低代码出错的可能性

  1. 开闭原则

即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。

  1. 迪米特法则

迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP)。
一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的public方法,我就调用这么多,其他的一概不关心。

  • 只和朋友交流

朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。

  1. public class Teacher {
  2. public void commond(GroupLeader groupLeader) {
  3. List<Girl> listGirls = new ArrayList<Girl>();
  4. for (int i = 0; i < 20; i++) {
  5. listGirls.add(new Girl());
  6. }
  7. groupLeader.countGirls(listGirls);
  8. }
  9. }

方法是类的一个行为,类竟然不知道自己的行为与其他类产生了依赖关系,这是不允许的。

正确的做法是:

  1. public class Teacher {
  2. public void commond(GroupLeader groupLeader) {
  3. groupLeader.countGirls();
  4. }
  5. }
  1. public class GroupLeader {
  2. private List<Girl> listGirls;
  3. public GroupLeader(List<Girl> _listGirls) {
  4. this.listGirls = _listGirls;
  5. }
  6. public void countGirls() {
  7. System.out.println("女生数量是:" + listGirls.size());
  8. }
  9. }

注意:类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。

  • 朋友间也是有距离的

一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-private(包类型,在类、方法、变量前不加访问权限,则默认为包类型)、protected等访问权限,是否可以加上final关键字等。
注意:迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。

总结:迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。

  1. 合成复用原则

    类A有2个方法,类B刚好需要调用这两个方法,我们第一可能会想到直接继承,这样“多快好省“,但随着业务进展,功能越来越复杂,A类需要增加其他方法,比如Method3 ,与B类毫无关联,将会大大增加耦合性,合用复用原则的核心就是使用关联,我们可以通过依赖、聚合、合成等关联方法,降低耦合,提高可维护性和降低维护成本。
    image.png