什么是设计模式

  • 设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

为什么要学习设计模式

  • 看懂源代码:如果你不懂设计模式去看Jdk、Spring、SpringMVC、IO等等等等的源码,你会很迷茫,你会寸步难行
  • 看看前辈的代码:你去个公司难道都是新项目让你接手?很有可能是接盘的,前辈的开发难道不用设计模式?
  • 编写自己的理想中的好代码:我个人反正是这样的,对于我自己开发的项目我会很认真,我对他比对我女朋友还好,把项目当成自己的儿子一样

设计模式分类

设计原则_抽象类

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式的六大原则

设计原则_抽象类_02

 

开放封闭原则(open close Principle)

  • 原则思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
  • 优点:单一原则告诉我们,每个类都有自己负责的职责,里氏替换原则不能破坏继承关系的体系。

里氏替换原则(Liskov Substitution Principle)

  • 原则思想:使用的基类可以在任何地方使用继承的子类,完美的替换基类
  • 描述:子类可以扩展父类的功能,但不能改变父类原有的 功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
  • 优点:增加程序的健壮性,即使增加了子类,原有的子类还可以继续运行,互不影响。

  1、子类可以实现父类的抽象方法,但不能覆盖父类的抽象方法,父类中已经实现好的方法,实际上是在设定一系列规范和契约,虽然它不强求所有子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。举例:

    

      public class C {
        public int func(int a, int b){
          return a+b;
          }
      }

 

      public class C1 extends C{
        @Override
        public int func(int a, int b) {
          return a-b;
        }
      }

 

      public class Client{
        public static void main(String[] args) {
          C c = new C1();
          System.out.println("2+1=" + c.func(2, 1));
        }
      }

运行结果:2+1=1

  上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。

2、子类可以有自己的个性

  在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:

  

    public class C {
      public int func(int a, int b){
        return a+b;
        }
    }

 

    public class C1 extends C{
      public int func2(int a, int b) {
        return a-b;
      }
    }

 

    public class Client{
      public static void main(String[] args) {
        C1 c = new C1();
        System.out.println("2-1=" + c.func2(2, 1));
      }
    }

运行结果:2-1=1

3、覆盖或实现父类的方法时输入参数可以被放大

当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,通过代码来讲解一下

    public class ParentClazz {
      public void say(CharSequence str) {
        System.out.println("parent execute say " + str);
      }
    }

    

    public class ChildClazz extends ParentClazz {
      public void say(String str) {
        System.out.println("child execute say " + str);
      }
    }

    /**
    * 测试
    */
    public class Main {
      public static void main(String[] args) {
        ArrayList list = new ArrayList();
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");

      }
    }

    // 执行结果:
    // parent execute say hello
    // child execute say hello

以上代码中我们并没有重写父类的方法,只是重载了同名方法,具体的区别是:子类的参数 String 实现了父类的参数 CharSequence。此时执行了子类方法,在实际开发中,通常这不是我们希望的,父类一般是抽象类,子类才是具体的实现类,如果在方法调用时传递一个实现的子类可能就会产生非预期的结果,引起逻辑错误,根据里氏替换的子类的输入参数要宽于或者等于父类的输入参数,我们可以修改父类参数为String,子类采用更宽松的 CharSequence,如果你想让子类的方法运行,就必须覆写父类的方法。代码如下:

  public class ParentClazz {
    public void say(String str) {
      System.out.println("parent execute say " + str);
    }
  }

  public class ChildClazz extends ParentClazz {
    public void say(CharSequence str) {
      System.out.println("child execute say " + str);
    }
  }

  public class Main {
    public static void main(String[] args) {
      ParentClazz parent = new ParentClazz();
      parent.say("hello");
      ChildClazz child = new ChildClazz();
      child.say("hello");
    }
  }

// 执行结果:
// parent execute say hello
// parent execute say hello

4、覆盖或实现父类的方法时输出结果可以被缩小

当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。代码实现案例如下:

  public abstract class Father {
    public abstract Map hello();
  }

  public class Son extends Father {
    @Override
    public Map hello() {
      HashMap map = new HashMap();
      System.out.println("son execute");
      return map;
    }
  }

  public class Main {
    public static void main(String[] args) {
    Father father = new Son();
    father.hello();
    }
  }

// 执行结果:
// son execute

依赖倒转原则(Dependence Inversion Principle)

  • 依赖倒置原则的核心思想是面向接口编程
  • 依赖倒置原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象类
  • 这个是开放封闭原则的基础,具体内容是:对接口编程,依赖于抽象而不依赖于具体。
  • 撒旦发射点发射点

  1、每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备

  2、变量的表面类型尽量是接口或者抽象类

  3、任何类都不应该从具体类派生

  4、尽量不要覆写基类的方法 
  如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响。

  5、结合里氏替换原则使用 
  里氏替换原则:父类出现的地方子类就能出现。结合本章我们得出了一个通俗的规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系。抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

    public interface IReader{
      //阅读者
      void Read(IRead read);
    }

    public interface IRead{
      //被阅读物
      void Read();

    }

    //文学经典类
    public class LiteraryClassic : IRead{
      //阅读文学经典
      public void Read(){
        Console.WriteLine("阅读文学经典,滋润内心心灵!");
      }
    }

    //小说类
    public class Novel : IRead{
      //阅读小说
      public void Read(){
        Console.WriteLine("阅读小说,放松一下!");
      }
    }

  //小明类
  public class XiaoMing : IReader{
    //阅读
    public void Read(IRead read){
      read.Read();
    }
  }

  static void Main(string[] args){
  XiaoMing xiaoming = new XiaoMing();//实例化对象小明

  IRead literaryClassic = new LiteraryClassic();
  //小明阅读文学经典
  xiaoming.Read(literaryClassic);

  IRead novel = new Novel();
  //小明阅读小说
  xiaoming.Read(novel);
  }

 

接口隔离原则(Interface Segregation Principle)

  • 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了生计和维护方便。所以上文中多次出现:降低依赖,降低耦合。

迪米特法则(最少知道原则)(Demeter Principle)

  定义

  迪米特法则(Law of Demeter, LoD)是1987年秋天由lan holland在美国东北大学一个叫做迪米特的项目设计提出的,它要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则(Least Knowledge Principle, LKP)。

单一职责原则(Principle of single responsibility)

  意义

  迪米特法则的意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。值得一提的是,这一法则却不仅仅局限于计算机领域,在其他领域也同样适用。比如,美国人就在航天系统的设计中采用这一法则。

 

  • 原则思想:一个方法只负责一件事情。
  • 描述:单一职责原则很简单,一个方法 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。 这是常识,几乎所有程序员都会遵循这个原则。
  • 优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。

 

  public class ParentClazz {
    public void say(String str) {
      System.out.println("parent execute say " + str);
    }
  }

  public class ChildClazz extends ParentClazz {
    public void say(CharSequence str) {
      System.out.println("child execute say " + str);
    }
  }

  public class Main {
    public static void main(String[] args) {
      ParentClazz parent = new ParentClazz();
      parent.say("hello");
      ChildClazz child = new ChildClazz();
      child.say("hello");
    }
  }

// 执行结果:
// parent execute say hello
// parent execute say hello