💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 设计模式的七大原则 23种设计模式遵循的原则 1. 单一职责原则。 2. 接口隔离原则。 3. 依赖倒转(倒置)原则。 4. 里氏替换原则。 5. 开闭原则ocp。 6. 迪米特法原则。 7. 合成复用原则。 达到目的: 1. 代码重用性:相同功能的代码,不用多次编写。 2. 可读性:编程规范,便于其他程序员阅读。 3. 可扩展性:添加新功能时方便。 4. 可靠性:添加新功能后对原功能没有影响。 5. 高内聚,低耦合。   ## 单一职责原则 单一职责原则指的是对类来说,一个类只负责一项职责,例如类A负责两个不同的职责1和职责2,修改职责1的代码会导致职责2执行出错,这个时候就要将类A分解成类职责1和类职责2(粒度分解为A1和A2)。也可以从方法的级别分解不同的职责。 举例如下: `SingleReposibility1.java` ~~~  zpackage cn.net.smrobot.design_principle;  ​  public class SingleResponsibility1 {      public static void main(String[] args) {          Vehicle vehicle = new Vehicle();          vehicle.run("公交");          vehicle.run("汽车");          vehicle.run("飞机");     }  ​  }  /**   * 方式1:这种方法违反了单一职责原则   * 例如传入“飞机”等不应该用run来执行   * 解决方法:根据不同的交通工具,分解成不同的类   */  class Vehicle {      public void run(String vehicle) {          System.out.println(vehicle + "在公路上跑");     }  }     ~~~ 这种方式导致不同种类的对象调用了同一个方法,例如飞机并不能适用于run方法。 > 改进方式:根据不同的交通工具,分解成不同的工具类 `SingleResponsibility2.java` ~~~  package cn.net.smrobot.design_principle;  ​  public class SingleResponsibility2 {      public static void main(String[] args) {          RoadVehicle roadVehicle = new RoadVehicle();          roadVehicle.run("摩托车");  ​          AirVehicle airVehicle = new AirVehicle();          airVehicle.run("飞机");  ​          WaterVehicle waterVehicle = new WaterVehicle();          waterVehicle.run("潜艇");     }  }  ​  /**   * 方案二:遵守了单一职责原则   * 但是改动大,将类分解的同时修改了客户端   * 改进:直接修改Vehicle类,而不修改其他类   */  class RoadVehicle{      public void run(String vehicle) {          System.out.println(vehicle + "在公路上跑...");     }  }  class AirVehicle {      public void run(String vehicle) {          System.out.println(vehicle + "在天空上飞...");     }  }  class WaterVehicle{      public void run(String vehicle) {          System.out.println(vehicle + "在水里游...");     }  } ~~~ 这种方式就遵循了`类级别`的单一职责原则,但是由于修改添加了太多的类,改动太大。 > 改进方法:直接修改Vehicle类,而不用添加其他类 `SingleResponsibility3.java` ~~~  package cn.net.smrobot.design_principle;  ​  public class SingleResponsibility3 {      public static void main(String[] args) {  ​     }  }  /**   * 方式3:没有对原来的类进行大修改,只是增加了方法   * 类级别上遵守单一职责原则,但是在方法上遵守了单一职责原则   */  class Vehicle1 {      public void run(String vehicle) {          System.out.println(vehicle + "在公路上跑");     }  ​      public void runAir(String vehicle) {          System.out.println(vehicle + "在天空飞...");     }  ​      public void runWater(String vehicle) {          System.out.println(vehicle + "在水里游...");     }  ​  } ~~~ 这种方法通过增加方法的方式,在`方法级别`上遵守了单一职责原则,但是类级别上遵守单一职责原则. **总结** 1. 单一职责原则可以降低类的复杂度。 2. 提高代码的可读性,降低变更代码引起的风险。 3. 如果类的逻辑简单,类中方法数量足够上,可以在方法级别上保证单一职责原则(`静态工具类`)。   ## 接口隔离原则 Interface Segregation Principle 接口隔离原则指的是客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。 大致意思就是当一个类实现一个接口时,如果接口中的一些方法并不会通过该类的依赖被使用到,那么就不要去实现该接口的所有方法了,而是将该接口分解成多个更小的接口,使得实现这些接口的中的方法都会被使用到。 举例: `InterfaceSegregation1.java` ~~~  package cn.net.smrobot.design_principle.interfaceSegregation;  ​  /**   * 接口隔离原则   */  public class InterfaceSegregation1 {      public static void main(String[] args) {  ​     }  }  ​  interface Interface1 {      void method1();  ​      void method2();  ​      void method3();  ​      void method4();  ​      void method5();  }  ​  /**   * B实现接口   */  class B implements Interface1 {      @Override      public void method1() {          System.out.println("B:method1");     }      @Override      public void method2() {          System.out.println("B:method2");     }      @Override      public void method3() {          System.out.println("B:method3");     }      @Override      public void method4() {          System.out.println("B:method4");     }      @Override      public void method5() {          System.out.println("B:method5");     }  }  ​  /**   * D也实现接口   */  class D implements Interface1{      @Override      public void method1() {          System.out.println("D:method1");     }      @Override      public void method2() {          System.out.println("D:method2");     }      @Override      public void method3() {          System.out.println("D:method3");     }      @Override      public void method4() {          System.out.println("D:method4");     }      @Override      public void method5() {          System.out.println("D:method5");     }  }  ​  /**   * A类通过接口依赖(使用)B类,但只会使用其中的1,2,3方法   */  class A {      public void depend1(Interface1 interface1) {          interface1.method1();     }      public void depend2(Interface1 interface1) {          interface1.method2();     }      public void depend3(Interface1 interface1) {          interface1.method3();     }  }  ​  /**   * C 类通过接口依赖类D,但是只是会使用其中的1,4,5个方法   */  class C {      public void depend1(Interface1 interface1) {          interface1.method1();     }      public void depend2(Interface1 interface1) {          interface1.method4();     }      public void depend3(Interface1 interface1) {          interface1.method5();     }  }  ​ ~~~ > 因此需要将接口拆分成多个子接口   ## 依赖倒转原则 `Dependence Inversion Principle` > 1. 高层模块不要依赖底层模块,二者都应该依赖其抽象 > > 2. 抽象不应该依赖细节,细节应该依赖抽象 > > 3. 中心思想就是面向接口编程 > > > 在java中,抽象指的是接口或者是抽象类,细节是对接口或抽象的实现 ~~~  public class DependenceInversion1 {      public static void main(String[] args) {          Person person = new Person();          person.receive(new Email());     }  }  ​  class Email {      public String getInfo() {          return "电子邮件信息:hello,world!";     }  }  ​  /**   * 依赖的方法1:   * 缺点:如果是其他信息来源(WeiXin),receive方法将不能使用   * 改进方法:引入一个抽象的接口IReceive,表示接受者,让Person与接口IReceive发生依赖   */  class Person {      public void receive(Email email) {          System.out.println(email.getInfo());     }  } ~~~ > Email参数设计为传递接口的方式 ~~~  public class DependenceInversion2 {      public static void main(String[] args) {          Person person = new Person();          person.receive(new Email());     }  }  ​  // 定义一个接受者接口  interface IReceive{      String getInfo();  }  // 实现接口  class Email1 implements IReceive{      @Override      public String getInfo() {          return "电子邮件信息:hello,world!";     }  }  ​  class WeiXin implements IReceive {  ​      @Override      public String getInfo() {          return "微信信息:hello,world!";     }  }  ​  /**   * 依赖的方法2:将接口作为参数传递   * 依赖接口更加抽象,稳定性更好   */  class Person1 {      public void receive(IReceive iReceive) {          System.out.println(iReceive.getInfo());     }  } ~~~ 依赖关系的实现: 1. 通过接口传递实现依赖 - 多态 `上面的例子` 2. 通过构造方法实现依赖 ~~~  interface IReceive {      String getInfo();  }  class Person {      private IReceive iReceive;      public Person(IReceive iReceive) { // 通过构造器传递一个已经实现好的接口实现类          this.iReceive = iReceive;     }  } ~~~ 3. 通过set方法实现依赖 ~~~  interface IReceive {      String getInfo();  }  class Person {      private IReceive iReceive;      // 通过set方法传递一个已经实现了接口的实现类      public setIReceive(IReceive iReceive) {          this.iReceive = iReceive;     }  } ~~~ 总结: 1. 底层模块尽量都要有抽象类或接口,会使程序的稳定性更好 2. 变量声明类型尽量使用抽象类或接口,这样我们在变量引用和实际对象间,就存在一个缓冲区,有利于程序扩展和优化 3. 继承时遵循里氏替换原则   ## 里氏替换原则 面向对象中继承体系的问题: 1. 增加程序之间的耦合性; 2. 如果一个父类被修改了,就要考虑其所有子类的功能,并且可能会导致所有子类的功能都故障; `如何正确使用继承` = `尽量满足里氏替换原则` > 如果对每个类型为T1的对象o1,都有类型T2的对象o2,使得在程序中用o2代替o1而不会对程序造成任何印象,即引用基类的地方必须能透明的使用其子类对象 * 在继承时,子类尽量不要重写父类的方法 * 在继承中实际上是程序的耦合性增高,在适当情况下,可以通过聚合,组合,依赖来解决问题。 ~~~  package cn.net.smrobot.design_principle.liskov;  ​  /**   * 里氏替换原则   */  public class Liskov1 {      public static void main(String[] args) {          A a = new A();          System.out.println(a.fun1(10, 2));          System.out.println("------------------");          B b = new B();          System.out.println(b.fun1(10, 2));          System.out.println(b.fun2(10, 2));     }  ​  ​  }  ​  class A {      public int fun1(int a, int b) {          return a - b;     }  }  ​  class B extends A {      @Override      public int fun1(int a, int b) {          return a + b;     }      public int fun2(int a, int b) {          return fun1(a, b) + 9;     }  } ~~~ 这里的fun1的方法被重写了,可能导致想要的功能不准确。 > 在实际编程过程中,我们常常会通过重写父类的方法完成新的功能,这样写会导致整个继承体系的复用性差,特别是在运行多态比较频繁的情况下。 > > 解决方法:取出原有的继承关系,抽象一个更加基础的基类。 修改方案: ~~~  package cn.net.smrobot.design_principle.liskov;  ​  public class Liskov2 {      public static void main(String[] args) {  ​     }  }  ​  // 创建一个更加基础的类  class Base {  ​  }  ​  class A1 extends Base{      public int fun1(int a, int b) {          return a - b;     }  }  ​  // B类不在继承A类,如果B类要用到A类,可以用依赖,聚合等关系来替换  class B1 extends Base{      //B类使用A类中的方法      private A1 a1 = new A1();  ​      public int fun1(int a, int b) {          return a + b;     }      public int fun2(int a, int b) {          return fun1(a, b) + 9;     }  ​      // 用到A类中的方法      public int fun3(int a, int b) {          return a1.fun1(a, b);     }  }  ​ ~~~ 总结: 1. 继承会带来程序的入侵性,降低程序的可移植性。 2. 里氏替换原则用来指导正确的使用继承关系。   ## 开闭原则 Open Closed Principle 开闭原则指的是,模块和函数应该对扩展开放(提供方),对修改关闭(使用方),用抽象构建框架,用实现扩展细节。 当软件需要变化时,尽量`通过扩展`软件的实体行为来实现变化,而不是通过修改已有的代码来实现变化。 **其他原则是为了实现开闭原则** ~~~  package cn.net.smrobot.design_principle.opendClose;  ​  /**   * 绘图类   */  public class Ocp1 {      public static void main(String[] args) {          GraphicEditor graphicEditor = new GraphicEditor();          graphicEditor.drawShape(new Rectangle());          graphicEditor.drawShape(new Circle());     }  }  ​  /**   * 绘制类   * 使用方   * 当添加绘制一个新的图形时需要在使用方这里修改代码   */  class GraphicEditor {  ​      public void drawShape(Shape shape) {          if (shape.type == 1) {              drawRectangle();         }          if (shape.type == 2) {              drawCircle();         }  ​     }      public void drawRectangle() {          System.out.println("绘制矩形");     }  ​      public void drawCircle() {          System.out.println("绘制圆形");     }  ​  }  ​  class Shape {      public int type;  }  ​  class Rectangle extends Shape {      public Rectangle() {          this.type = 1;     }  }  ​  class Circle extends Shape {      public Circle() {          this.type = 2;     }  } ~~~ > 改进思路:可以将Shape类改成抽象类,然后让子类重写各自的实现。 ~~~  package cn.net.smrobot.design_principle.opendClose;  ​  /**   * 绘图类   */  public class Ocp1 {      public static void main(String[] args) {          GraphicEditor graphicEditor = new GraphicEditor();          graphicEditor.drawShape(new Rectangle());          graphicEditor.drawShape(new Circle());     }  }  ​  /**   * 绘制类   * 直接调用draw方法,多态的思想   */  class GraphicEditor {  ​      public void drawShape(Shape shape) {          shape.draw();     }        }  ​  abstract class Shape {      public int type;            protected void draw();  }  ​  class Rectangle extends Shape {      public Rectangle() {          this.type = 1;     }            @Override      public void draw() {          System.out.println("绘制矩形");     }  }  ​  class Circle extends Shape {      public Circle() {          this.type = 2;     }            @Override      public void draw() {          System.out.println("绘制圆形");     }  }  ​  class Triangle extends Shape {      public Circle() {          this.type = 2;     }            @Override      public void draw() {          System.out.println("绘制三角形");     }  }  ​ ~~~ 总结: 1. 对于使用方,尽量不要修改提供方的内容,而是扩展提供方的内容。 2. 尽量使用的抽象的方式来修改程序的功能。   ## 迪米特法则 Demeter Principle > 1. 一个对象应该对其他对象保持最少的了解。 > > 2. 又叫最少知道原则,即一个类对自己依赖的类知道得越少越好,即一个类尽量进行封装。 > > 3. 更简单的定义:只和直接朋友通信。 > 直接朋友: 将出现在`成员变量,方法参数,方法返回值`中的类称为直接朋友,而以局部变量的方式出现的类不是直接朋友,因此别的类尽量不要以局部变量的方式出现在类的内部。 总计: 1. 迪米特法则用来降低类之间的耦合关系。 2. 只是尽量减少类之间的依赖关系,而不是完全不依赖。   ## 合成复用原则 > 尽量使用合成或者聚合的方式,而不是使用继承的方式   ## 总结 1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混合在一起。 2. 面向接口编程,而不是面向实现编程。 3. 主要目的是让代码达到松耦合。   ## 类之间的关系 UML Unified Modeling Language 统一建模语言,是一套帮助软件系统分析和设计的语言工具。 由一系列符号(类似于数学符号)和图案组成用来表示**类之间的关系**。 由如下几种关系: 1. Dependency 依赖 2. Association 关联 3. Generalization 泛化/继承 4. Realization 实现 5. Aggregation 聚合关系,关联的一种,通过set方法引入 6. Composite 组合关系,关联关系的一种,通过成员属性A a = new A();的方法引入 **类图分类** 1. 用例图 use case 2. 静态结构图:`类图`,对象图,包图,组件图,部署图 3. 动态行为图:交互图,状态图,活动图 ## 依赖关系 只要在类中用到了对方,他们之间就存在依赖关系。 :-: ![](https://img.kancloud.cn/d7/bb/d7bba041108c554faced1921cb105e6d_534x306.png) 使用的位置: 1. 类的成员属性 2. 方法的返回类型 3. 方法接收的参数类型 4. 方法中的局部变量。   ## 泛化和实现关系 泛化关系:继承关系,也是依赖关系的一种特例。 :-: ![](https://img.kancloud.cn/87/a3/87a3fac7acd4ff3d865aafcb031acaff_251x262.png) 实现关系:即A类实现B接口,也是依赖关系的一种特例。 :-: ![](https://img.kancloud.cn/ea/10/ea106f5d46d2906c6aff34c756ec81a9_289x250.png)   ## 关联关系 类与类之间的关系,是依赖关系的一种特例。 关联关系具有导航性:即双向关系或单向关系。 ~~~  // 单向一对一关系  public class Person() {      private IDCard card;  }  public class IDCard() {}  ​  // 双向一对一关系  public class Person() {      private IDCard card;  }  public class IDCard() {      private Person person;  } ~~~   ## 聚合关系 表示整体和部分的关系,`整体和部分可以分开`,聚合关系是关联关系的特例。具有导航性和多重性。`对象的引入通过set方法或者构造方法`实现。 ~~~  // 鼠标类和显示器类都可以和Computer类分开,因此是聚合关系  public class Computer{      private Mouse mouse;            private Monitor monitor;            // set方法        } ~~~ :-: ![](https://img.kancloud.cn/68/48/68481131c3d904a590f16d170ff92def_515x203.png)   ## 组合关系 整体和部分是不可以分开的称为组合关系,对象的引入通过直接new实现。 ~~~  // 鼠标类和显示器类在Computer一创建就会被创建,因此是不可分割的,属于组合关系  public class Computer{      private Mouse mouse = new Mouse();            private Monitor monitor = new Mouse();        } ~~~ :-: ![](https://img.kancloud.cn/33/fe/33fe059712904edc38053815ef7c016d_595x215.png) 聚合关系和组合关系是可以混合使用,主要是看整体和部分是否可以分开。 > 如果Person类中定义了对IDCard的级联删除,那么Person类和IDCard类就是组合关系。不一定直接使用new才是组合关系。