设计模式相关视频,讲得挺好的,至少比马士兵的好,不足的是选集没有排好序,不能自动连播,而且还在连载中

TuringPlanet的设计模式

视频配套代码

七大原则

设计模式七大原则

设计模式六大原则分别为:

  1. 单一职责原则(SRP): 一个类只应该有一个引起它变化的原因,即一个类只负责一项职责。(有点DDD那味儿)
    • 高内聚:避免大而全,避免不相关功能的耦合

    • 低耦合:减少所需要依赖和被依赖的类

  2. 开放封闭原则(OCP):对扩展开放,对修改关闭。当需要改变一个程序的功能或给它增加新功能时,可以通过增加代码来实现,而不是修改已有的代码。
    • 测试简单
    • 可复用性变强
    • 稳定性变高
  3. 里氏替换原则(LSP):子类可以替换其父类并且仍然产生正确的结果。这表明在使用继承时需要非常小心,子类和父类之间的关系应该是is-a。
  4. 接口隔离原则(ISP):客户端不应该依赖那些它不需要使用的接口。一个类对另一个类的依赖性应该是建立在最小的接口上。
  5. 依赖倒置原则(DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现具体实现应该依赖于抽象。这个原则的目的是为了减少类之间的耦合。
  6. 迪米特法则(LoD):也称最少知道原则,即一个对象应该对其他对象有最少的了解。一个类应该对自己需要耦合或调用的类知道得最少这样可以降低类之间的耦合度提高系统的可维护性和可扩展性。缺点是有可能存在大量的中介类,增加系统的复杂度
  7. 合成复用原则:尽量使用对象聚合/组合,而不是继承来实现软件复用的目的。

合成复用原则_聚合、组合、集成

创建型模式

创建型模式

  • 帮助我们创建类或者对象
  • 核心思想:把对象的创建和使用分离,使两者能相对独立地变换

单例模式

  • 概念:保证一个类只有一个实例。Spring中用的很多。

  • 示例:分为懒汉式和饿汉式

    • 懒汉式:用的时候再实例化,需要注意线程安全问题

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      /**
      * 苹果
      */
      public class Apple {
      /**
      * 这是亚当和夏娃吃的那个苹果,独一无二
      * volatile防止指令重排
      */
      private static volatile Apple INSTANCE;

      /**
      * 私有化构造器,避免被实例化,但是反射还是可以做到。
      */
      private Apple(){}

      public static Apple getInstance(){
      if (INSTANCE == null){
      synchronized (Apple.class){
      //防止后续线程在INSTANCE 实例化后,将要return时,
      //其他线程抢到了sync的锁,又new一次
      if (INSTANCE == null){
      INSTANCE = new Apple();
      }
      }
      }
      return INSTANCE;
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class Apple1 {
      /**
      * 私有化构造器,避免被实例化,但是反射还是可以做到。
      */
      private Apple1(){}

      private static Apple1 INSTANCE;

      public static Apple1 getInstance(){
      return AppleHolder.APPLE;
      }

      /**
      * 完美写法,私有内部类在使用时才加载,所以Apple1在使用时才实例化,
      * 同时JVM底层做了线程安全
      */
      private static class AppleHolder {
      public static final Apple1 APPLE = new Apple1();
      }

      }
    • 饿汉式:类加载完就实例化了

      1
      2
      3
      4
      5
      6
      7
      public class Apple2 {
      private static final Apple2 INSTANCE = new Apple2();
      private Apple2(){};
      public static Apple2 getInstance(){
      return INSTANCE;
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      /**
      * 写法有点怪,但是确实能保证只有一个实例
      * 并且线程安全,同时可以防止被反序列化,
      * 因为枚举类没有构造器。
      * 在枚举类型中,实例字段被final修饰,并且是一个在类加载时就被初始化的静态常量。
      * 因此,枚举实现的单例模式是一种饿汉式的变体,也可以看作是一种预先实例化的单例模式。
      */
      public enum Apple3 {
      INSTANCE;
      }

工厂模式

  • 概念:定义了一个用于创建对象的接口,但让子类决定实例化哪个类

  • 示例:下面是一个简单的工厂模式示例代码,实现了在不同的国家/地区生成不同格式的日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public interface DateFormat {
    String format(LocalDate date);
    }

    public class ChinaDateFormat implements DateFormat {
    @Override
    public String format(LocalDate date) {
    return date.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
    }
    }

    public class USDateFormat implements DateFormat {
    @Override
    public String format(LocalDate date) {
    return date.format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));
    }
    }

    public class DateFormatFactory {
    public enum Country {
    CHINA, US
    }

    public static DateFormat getFormatter(Country country) {
    switch (country) {
    case CHINA:
    return new ChinaDateFormat();
    case US:
    return new USDateFormat();
    default:
    throw new IllegalArgumentException("Unsupported country: " + country);
    }
    }
    }

    笔者觉得跟策略模式很像……只是这个会返回实例,策略模式是根据选择执行代码。

抽象工厂模式

  • 概念:一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

    有点拗口,举个例子。小说有魔法,武侠两种类型,他们都有攻击手段,能量两种要素

    • 魔法:魔咒,魔力(金木水火土等)
    • 武侠:武功,内力(至阳至刚,至阴至柔等)

    那么抽象工厂就可以分为

    • 魔法工厂,可以创建魔咒和魔力
    • 武侠工厂,可以创建武功和内力

    其实就是对工厂分类,一种工厂只能生产同一对象族的东西

  • 代码实现自己去写了,上面都说得这么明白了

简单工厂_vs_工厂方法_vs_抽象工厂

建造者模式

  • 概念:主要目的是将一个复杂对象的构建与表示分离,从而可以使用相同的构建过程来创建不同的表示。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    //地址类
    @Data
    public class Location {

    private String street;
    private String roomNo;
    }
    //学生类
    public class Student {
    private String name;
    private Integer age;
    private Location loc;

    /**
    * 建造者,可以使用loc方法给Location对象设置值,
    * 也可以分开通过roomNo和street方法设置值
    * 做到复杂对象可以简单的构建
    */
    public static class StudentBuilder {
    private String name;
    private Integer age;
    private String street;
    private String roomNo;

    public StudentBuilder name(String name) {
    this.name = name;
    return this;
    }

    public StudentBuilder age(Integer age) {
    this.age = age;
    return this;
    }

    public StudentBuilder roomNo(String roomNo) {
    this.roomNo = roomNo;
    return this;
    }

    public StudentBuilder street(String street) {
    this.street = street;
    return this;
    }

    public StudentBuilder loc(String street, String roomNo) {
    this.roomNo = roomNo;
    this.street = street;
    return this;
    }

    public Student build() {
    Student student = new Student();
    if (this.street != null || this.roomNo != null) {
    Location location = new Location();
    location.setRoomNo(this.roomNo);
    location.setStreet(this.street);
    student.loc = location;
    }
    student.name = this.name;
    student.age = this.age;
    return student;
    }
    }
    }

使用建造者模式时,客户端关注的是产品的组装过程,而不是产品的创建细节,因此更加灵活,适用于需要灵活组装、创建复杂对象的场景。同时也方便扩展和修改已有的构建流程,提高了代码的可维护性和可读性。

原型模式

没啥好说的,就是java的clonable接口,浅拷贝和深拷贝的问题。深拷贝就是里面的对象,比方说上面的Student.loc,要实现深拷贝,那Location也要实现clonable接口

结构型模式

结构性模式

  • 涉及如何组合各种对象以便获得更好、更灵活的结构
  • 更多地通过组合与运行期的动态结合来实现更灵活的功能

适配器模式

  • 概念:将两个不兼容的接口之间进行桥接,以使它们可以一起工作。适配器模式通过创建一个中间层,将客户端和目标接口分离,从而达到复用已有的代码的目的。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Main {

    public static void main(String[] args) throws IOException {
    InputStream is = new FileInputStream("/home/temp/test.txt");
    //isr就是适配器,让br适配is
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    String line = null;
    while ((line = br.readLine()) != null) {
    System.out.println(line);
    }
    }
    }

桥接模式

面试里不常问到。有点像套壳贴牌。

  • 概念:通过将抽象部分与实现部分分离开来,使它们可以独立地变化。桥接模式通过将抽象和实现解耦,从而可以减少系统中类的数量,并简化类的继承关系。

  • 示例:

    定义实现部分接口:

    1
    2
    3
    4
    public interface DrawingAPI {
    void drawCircle(double x, double y, double radius);
    void drawRectangle(double x1, double y1, double x2, double y2);
    }

    定义具体实现部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class DrawingAPI1 implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
    System.out.printf("API1.circle at %f:%f radius %f%n", x, y, radius);
    }

    @Override
    public void drawRectangle(double x1, double y1, double x2, double y2) {
    System.out.printf("API1.rectangle at %f:%f to %f:%f%n", x1, y1, x2, y2);
    }
    }

    public class DrawingAPI2 implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
    System.out.printf("API2.circle at %f:%f radius %f%n", x, y, radius);
    }

    @Override
    public void drawRectangle(double x1, double y1, double x2, double y2) {
    System.out.printf("API2.rectangle at %f:%f to %f:%f%n", x1, y1, x2, y2);
    }
    }

    定义抽象部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public abstract class Shape {
    protected DrawingAPI drawingAPI;

    public Shape(DrawingAPI drawingAPI) {
    this.drawingAPI = drawingAPI;
    }

    public abstract void draw();
    }

    public class Circle extends Shape {
    private double x, y, radius;

    public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
    super(drawingAPI);
    this.x = x;
    this.y = y;
    this.radius = radius;
    }

    @Override
    public void draw() {
    drawingAPI.drawCircle(x, y, radius);
    }
    }

    public class Rectangle extends Shape {
    private double x1, y1, x2, y2;

    public Rectangle(double x1, double y1, double x2, double y2, DrawingAPI drawingAPI) {
    super(drawingAPI);
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    }

    @Override
    public void draw() {
    drawingAPI.drawRectangle(x1, y1, x2, y2);
    }
    }

    在上述代码中,我们定义了实现部分接口DrawingAPI和具体实现部分DrawingAPI1DrawingAPI2。在抽象部分中,我们定义了抽象类Shape和具体形状类CircleRectangle。可以看到,抽象部分持有一个实现部分对象的引用,并将抽象部分的操作转发给实现部分对象,从而实现了抽象部分与实现部分的解耦。

    使用桥接模式,我们可以将系统中的抽象部分和实现部分解耦开来,减少类的数量,并简化类的继承关系。同时,它还能够提高系统的可扩展性和可维护性,使系统更加灵活,易于修改和扩展。

组合模式

  • 概念:将对象组织成树形结构,以表示部分和整体之间的层次关系,并且使得用户对单个对象和组合对象的使用具有一致性。组合模式通过递归组合来实现对整个树形结构的操作,使得用户在使用时无需知道具体处理的是单个对象还是组合对象

    • Component:抽象构件,声明了组合中所有对象共有的接口和默认行为。
    • Leaf:叶子构件,表示组合中的叶子对象,叶子对象没有子节点。
    • Composite:容器构件,表示组合中的容器对象,容器对象可以包含其他组合对象和叶子对象,并且提供了管理其子部件的方法。
  • 示例:

    以下是一个简单的组合模式示例代码,用于展示不同部门和员工的层次关系:

    定义抽象构件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public abstract class Department {
    protected String name;

    public Department(String name) {
    this.name = name;
    }

    public abstract void add(Department department);
    public abstract void remove(Department department);
    public abstract void display(int depth);
    }

    定义叶子构件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Employee extends Department {
    public Employee(String name) {
    super(name);
    }

    @Override
    public void add(Department department) {
    // 叶子节点无法添加子节点
    }

    @Override
    public void remove(Department department) {
    // 叶子节点无法移除子节点
    }

    @Override
    public void display(int depth) {
    System.out.println("-".repeat(depth) + name);
    }
    }

    定义容器构件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class DepartmentComposite extends Department {
    private List<Department> subDepartments = new ArrayList<>();

    public DepartmentComposite(String name) {
    super(name);
    }

    @Override
    public void add(Department department) {
    subDepartments.add(department);
    }

    @Override
    public void remove(Department department) {
    subDepartments.remove(department);
    }

    @Override
    public void display(int depth) {
    System.out.println("-".repeat(depth) + name);

    for (Department department : subDepartments) {
    department.display(depth + 2);
    }
    }
    }

    在上述代码中,我们定义了抽象构件Department,并通过叶子构件Employee和容器构件DepartmentComposite来实现其具体子类。容器构件中包含了一个子部件列表,可以添加和移除子部件,并在展示时递归显示其所有子部件。通过使用组合模式,我们可以将部分和整体都看作是“部门”,并且可以统一对它们进行操作,使得客户端代码更加简洁并易于扩展。

装饰器模式

  • 概念:动态地给一个对象增加一些额外的职责,同时又不改变该对象的结构。

  • 示例:装饰器模式通过将对象包装在装饰器对象中,从而使得装饰器对象与被包装的对象拥有相同的接口,以此来扩展对象的功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 饮料,不用抽象类也可以。抽象类更符合这个场景,
    * 比如可以把茶替换椰奶等
    */
    public interface Drink {

    /**
    * 口味
    * @return
    */
    String taste();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 茶
    */
    public class Tea implements Drink{

    public String taste(){
    return "茶";
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    * 加柠檬,或者定义其他的,只要是类似结构
    */
    public class LemonDecorator implements Drink {
    private Drink drink;

    public LemonDecorator(Drink drink) {
    this.drink = drink;
    }

    /**
    * 装饰器对原有方法加强
    * @return
    */
    @Override
    public String taste() {
    return "柠檬" + drink.taste();
    }

    /**
    * 装饰器自有增强方法
    * @param tenth
    * @return
    */
    public String sugar(int tenth) {
    return tenth + "分糖";
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Main {
    public static void main(String[] args) {
    Drink drink = new Tea();
    LemonDecorator lemonDecorator = new LemonDecorator(drink);
    //对原有方法增强
    //柠檬茶
    System.out.println(lemonDecorator.taste());
    //装饰器自己的方法
    //甜度
    System.out.println(lemonDecorator.sugar(5));
    //装饰器也可以变成Drink,实现多态,但是没有甜度的方法
    drink = lemonDecorator;
    //还是柠檬茶
    System.out.println(drink.taste());
    }
    }

    这个也是套壳,但是像手机套壳,内里还是手机。桥接模式是换内里,然后内里有相同的方法,不同的实现逻辑

门面模式(Facade)

  • 概念:提供了一个简化的接口,来为复杂的子系统提供一个统一的界面。外观模式的目的是简化客户端与子系统之间的交互,减少客户端代码的复杂度。

  • 示例:下面是一个使用外观模式的示例,假设我们有一家咖啡店,客户可以在该咖啡店订购不同类型的咖啡和添加不同的调料。为简化客户端订购咖啡的流程,我们可以使用外观模式,创建一个咖啡外观类作为客户端与咖啡店后台的接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class CoffeeFacade {
    private Americano americano;
    private Cappuccino cappuccino;
    private Latte latte;

    public CoffeeFacade() {
    this.americano = new Americano();
    this.cappuccino = new Cappuccino();
    this.latte = new Latte();
    }

    public void orderAmericano() {
    americano.makeCoffee();
    }

    public void orderCappuccino() {
    cappuccino.makeCoffee();
    }

    public void orderLatte() {
    latte.makeCoffee();
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public abstract class Coffee {
    public abstract void makeCoffee();
    }

    public class Americano extends Coffee {
    @Override
    public void makeCoffee() {
    System.out.println("Making Americano...");
    }
    }

    public class Cappuccino extends Coffee {
    @Override
    public void makeCoffee() {
    System.out.println("Making Cappuccino...");
    }
    }

    public class Latte extends Coffee {
    @Override
    public void makeCoffee() {
    System.out.println("Making Latte...");
    }
    }

    在上述代码中,我们创建了一个咖啡外观类CoffeeFacade,该类持有各种类型的咖啡对象,客户端只需要调用相应的方法便可以订购对应类型的咖啡。同时我们也创建了Coffee抽象类和其具体子类AmericanoCappuccinoLatte作为子系统,实现了各自特定类型咖啡的制作过程。

    通过使用外观模式,我们将复杂的咖啡店后台与客户端逻辑解耦,并提供了一个简单、易于使用的接口,使得客户端订购咖啡的流程更加简单、直观。

享元模式

  • 概念:通过共享对象来减少内存消耗和提高性能。享元模式的核心思想是将多个相似对象的共同部分抽象出来,作为一个共享对象来使用,而将不同的部分作为外部状态。比如 线程池
  • 示例:看下线程池吧

代理模式

面试考的最多的,Spring AOP用的最多的

  • 概念:通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。

  • 常见的代理模式有静态代理,动态代理,cglib代理

    • 静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。(笔者觉得这是装饰器模式……)

    • 动态代理:JDK提供的API,基于反射生成新的接口实现类(生成的字节码直接在内存中),在调用每个方法时,会调用invoke,走invocationHandler里的逻辑。不用关心代理类,只需要在运行阶段才指定代理哪一个对象,但是只能面向接口类。

      动态代理字节码输出

      通过java.lang.System#setProperty方法设置系统属性

      sun.misc.ProxyGenerator.saveGeneratedFiles=true

      jdk.proxy.ProxyGenerator.saveGeneratedFiles=true

      上述属性二者择其一,具体设置哪个需要依照JDK版本进行选择,老版本的前者,新版本的后者

    • cglib代理:也是利用的asm,不同的是它可以代理非接口类,因为它是通过生成目标类的子类来完成的代理。

  • 代码示例:

    我就问,这个装饰器模式有啥区别……

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Tea implements Drink{
    @Override
    public String taste() {
    System.out.println("获取饮料类型");
    return "茶";
    }
    }

    public class TeaProxy extends Tea{
    private Tea tea;
    public TeaProxy(Tea drink){
    this.tea = drink;
    }

    @Override
    public String taste() {
    System.out.println("--before--");
    String taste = this.tea.taste();
    System.out.println("--after--");
    return taste;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public interface Drink {
    String taste();
    }

    public class Tea implements Drink{
    @Override
    public String taste() {
    System.out.println("获取饮料类型");
    return "茶";
    }
    }

    public class JdkProxyMain {

    public static void main(String[] args) {
    //输出代理类
    System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    Drink o = (Drink) Proxy.newProxyInstance(Tea.class.getClassLoader()
    , Tea.class.getInterfaces()
    , (proxy, method, args1) -> {
    System.out.println("--before--");
    Object invoke = method.invoke(Tea.class.getConstructor().newInstance());
    System.out.println("--after--");
    return invoke;
    });
    //代理对象调用方法
    System.out.println(o.taste());
    }
    }

    看下反编译之后的代理类

    反编译后代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 喜茶
    */
    public class HeaTea {

    public String fruit(){
    System.out.println("执行fruit方法");
    return "芒果";
    }

    public String sugar(int tenth){
    System.out.println("执行sugar方法");
    return tenth + "分糖";
    }

    }

    public class CglibProxyMain {

    public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    //设置父类
    enhancer.setSuperclass(HeaTea.class);
    enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
    System.out.println("--before--");
    Object o1 = methodProxy.invokeSuper(o, objects);
    System.out.println("--after--");
    return o1;
    });
    HeaTea o = (HeaTea) enhancer.create();
    //输出代理出来的子类 com.example.designmodel.proxy.HeaTea$$EnhancerByCGLIB$$3fe21093
    System.out.println(o.getClass().getName());
    System.out.println(o.sugar(5)+o.fruit());
    }
    }

行为型模式

行为型模式

  • 用于描述类或者对象是怎样交互和如何分配职责的。
  • 涉及到算法和对象间的职责分配,描述一组对象应该如何协作来完成一个整体的任务

责任链模式

  • 概念:可以将多个对象组成一条链,并依次处理请求,直到其中一个对象能够处理该请求为止。在责任链模式中,每个对象都拥有处理请求的机会,但是如果自己不能够处理该请求,则会将请求转发给下一个对象进行处理。这样的设计可以大大降低对象之间的耦合度,使系统更加灵活和可扩展。
  • 示例:Spring的Filter链就是。

命令模式

  • 概念:它将请求封装成一个对象,使得可以将不同的请求进行参数化和操作,从而实现请求的解耦和队列化。核心思想是将请求封装成一个命令对象,该命令对象包含了请求的相关信息,例如请求的接收者、请求的操作等。同时,还可以使用回调函数来处理请求的结果,从而实现请求与响应之间的解耦。
  • 示例:命令模式常被用于实现撤销重做事务等功能也可以用于实现多级菜单快捷键等用户界面交互功能

解释器模式

  • 概念:用于定义一个语言或规则的语法,并提供一个解释器来解释语言中的句子或表达式。
  • 示例:脚本或底层开发才会用到,可以不用了解

迭代器模式

  • 概念:提供了一种访问容器(如列表、集合等)元素的方式,不必暴露容器的内部结构。迭代器模式将遍历与容器的实现分离开来,并为容器提供了一个统一的接口,使得不同类型的容器都可以使用相同的遍历方式。
  • 示例:看下Iterator的源码吧。通常包括两个核心角色:迭代器 (Iterator) 和容器 (Container)。

中介者模式(Mediator)

  • 概念:通过引入一个中介者对象,可以让对象间不直接交互,而是通过中介者来实现相互通信和协作。

  • 示例:在中介者模式中,通常包括三个角色:中介者 (Mediator)、具体中介者 (Concrete Mediator) 和同事类 (Colleague)。在中介者模式中,同事类之间不直接交互,而是通过中介者来传递消息和协作,因此可以降低对象间的耦合性,使得系统更加易于维护和扩展。

    1
    2
    3
    4
    5
    public interface Mediator {
    void sendMessage(String message, Colleague colleague);
    void register(Colleague colleague);
    void remove(Colleague colleague);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class ConcreteMediator implements Mediator {
    private List<Colleague> colleagues;
    public ConcreteMediator() {
    colleagues = new ArrayList<>();
    }
    @Override
    public void sendMessage(String message, Colleague colleague) {
    for (Colleague c : colleagues) {
    if (!c.equals(colleague)) {
    c.receiveMessage(message);
    }
    }
    }
    @Override
    public void register(Colleague colleague) {
    colleagues.add(colleague);
    colleague.setMediator(this);
    }
    @Override
    public void remove(Colleague colleague) {
    colleagues.remove(colleague);
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public interface Colleague {
    void sendMessage(String message);
    void receiveMessage(String message);
    void setMediator(Mediator mediator);
    Mediator getMediator();
    }

    public class ConcreteColleague implements Colleague {
    private Mediator mediator;
    @Override
    public void sendMessage(String message) {
    mediator.sendMessage(message, this);
    }
    @Override
    public void receiveMessage(String message) {
    System.out.println("收到消息:" + message);
    }
    @Override
    public void setMediator(Mediator mediator) {
    this.mediator = mediator;
    }
    @Override
    public Mediator getMediator() {
    return mediator;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Client {
    public static void main(String[] args) {
    Mediator mediator = new ConcreteMediator();
    Colleague colleague1 = new ConcreteColleague();
    Colleague colleague2 = new ConcreteColleague();
    mediator.register(colleague1);
    mediator.register(colleague2);
    colleague1.sendMessage("Hello, colleague2!");
    colleague2.sendMessage("Hi, colleague1!");
    }
    }

备忘录模式/快照模式

  • 概念:用于在不破坏封装性的前提下,保存和恢复对象的内部状态
  • 示例:备忘录模式通过一个备忘录角色来存储对象的状态,并且可以在需要时恢复对象的状态,从而实现撤销和重做等功能。通常和命令模式结合使用。但是,在使用备忘录模式时,需要注意备忘录对象的存储和管理,避免占用过多的内存空间。

观察者模式/发布-订阅模式

  • 概念:定义了一个一对多的依赖关系,当被依赖的对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。这种模式在应用程序中广泛使用,例如处理事件、用户界面设计,消息中间件和监听器(Listener)等。观察者模式的优点是遵循开闭原则,可以在不修改代码的情况下增加新的主题和观察者;同时可以实现一种松耦合的关系让主题和观察者之间的依赖减小,提高了系统的灵活性和可维护性。但是,使用观察者模式也有缺点,例如可能会引起内存泄漏问题等。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 定义发布的事件,通常携带源数据信息,参考下js的监听事件
    */
    @Data
    public class MyEvent {
    private String content;
    private String title;
    private String author;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public interface Observer {
    void receive(MyEvent event);
    }

    /**
    * 主题
    */
    public class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void registerObserver(Observer observer) {
    this.observers.add(observer);
    }

    public void publish() {
    MyEvent myEvent = new MyEvent();
    myEvent.setTitle("静夜思");
    myEvent.setAuthor("李白");
    myEvent.setContent("床前明月光,疑是地上霜。举头望明月,低头思故乡。");
    for (Observer observer : observers) {
    observer.receive(myEvent);
    }
    }

    public static void main(String[] args) {
    Subject subject = new Subject();
    subject.registerObserver(event -> System.out.println("提取作者:"+event.getAuthor()));
    subject.registerObserver(event -> System.out.println("提取题目:"+event.getTitle()));
    subject.registerObserver(event -> System.out.println("诗句数量:"+event.getContent().split("。").length));
    subject.publish();
    }
    }

状态模式

  • 概念:它允许对象在不同的状态下有不同的行为。

  • 示例:状态模式-掘金

    反正我是不会用这个东西的,给代码挖坑。什么玩意儿……

策略模式

这个应该会经常问到

  • 概念:定义了一系列算法族,将每个算法封装到具有公共接口的独立类中,并且这些算法之间可以相互替换。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import com.alibaba.fastjson.JSON;
    import lombok.Data;

    import java.util.Arrays;
    import java.util.List;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    @Data
    public class Resp<T> {

    private T data;

    /**
    * 转换List,生成新的类
    *
    * @param origData 原始数据
    * @param func 转换的自定义策略,示例直接用了lambda函数,也可以定义成类
    * @param <T> 泛型,原始数据类型
    * @param <R> 泛型,返回数据类型
    * @return
    */
    public static <T, R> Resp<R> toList(List<T> origData, Function<List<T>, R> func) {
    Resp<R> tResp = new Resp<>();
    R apply = func.apply(origData);
    tResp.setData(apply);
    return tResp;
    }

    public static void main(String[] args) {
    List<String> strings = Arrays.asList("1", "2", "3");
    //转换成整型
    Resp<List<Integer>> listResp = Resp.toList(strings, origList -> origList
    .stream()
    .map(Integer::valueOf)
    .collect(Collectors.toList())
    );
    //转换成浮点
    Resp<List<Double>> listResp2 = Resp.toList(strings, origList -> origList
    .stream()
    .map(Double::valueOf)
    .collect(Collectors.toList())
    );
    System.out.println(JSON.toJSONString(listResp,true));
    System.out.println(JSON.toJSONString(listResp2,true));
    }

    }

模板方法模式

  • 概念:定义了一个算法的框架,并在一个方法中定义了该算法的各个步骤,但是把一些步骤的具体实现留给子类去完成。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public abstract class AbstractClass {
    public void templateMethod() {
    operation1();
    operation2();
    operation3();
    }
    abstract void operation1();
    abstract void operation2();
    abstract void operation3();
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ConcreteClass extends AbstractClass {
    @Override
    void operation1() {
    System.out.println("ConcreteClass: operation1");
    }
    @Override
    void operation2() {
    System.out.println("ConcreteClass: operation2");
    }
    @Override
    void operation3() {
    System.out.println("ConcreteClass: operation3");
    }
    }

    1
    2
    3
    4
    5
    6
    7
    public class Client {
    public static void main(String[] args) {
    AbstractClass abstractClass = new ConcreteClass();
    abstractClass.templateMethod();//固定执行了子类的operation1,2,3
    }
    }

访问者模式

  • 概念:将算法和对象结构分离开来,使得可以在不修改对象结构的情况下,增加新的操作和算法。
  • 示例:访问者模式的核心在于同一个事物不同视角下的访问信息不同,比如看一场篮球比赛,外行人关注的是是否进球,内行人看的是球员的技术,球队的配合等。
  • 思考:这跟策略模式有啥区别?

策略模式侧重于主体结构的变化,而访问者模式更强调主体结构不变,变化的是传入的访问者。

相同点是,本质上来讲,都是面向接口的编程。