代理模式

代理模式

介绍

代理模式作为设计模式的一种,在各种框架体系中均有应用,代理代理,顾名思义,代替某个对象处理事情.代理模式:为对象提供一个替身,以控制对这个对象的访问,从而通过代理对象访问目标对象,好处是在目标对象基础上增加额外的功能操作,扩展目标对象的功能

Java体系中代理模式有三种形式

  • 静态代理
  • 动态代理
    • JDK 动态代理
    • CGLIB 动态代理

静态代理

1.定义一个接口

2.定义一个实现类,实现该接口

3.定义一个代理类,同样实现该接口

4.代理类持有实现类的引用

5.通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情

java 复制代码
//1.定义一个接口
public interface SmsService {

    String send(String message);

}
//2.定义一个实现类,实现该接口
public class SmsServiceImpl implements SmsService {
  @Override
  public String send(String message) {
    System.out.println("message = " + message);
    return message;
  }
}
//3.定义一个代理类,同样实现该接口
public class SmsProxy implements SmsService {

  //4.代理类持有实现类的引用
  private final SmsService smsService;

  //多态,使得代理类可以代理所有实现了SmsService接口的实现类
  public SmsProxy(SmsService smsService) {
    this.smsService = smsService;
  }

  //5.通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情
  @Override
  public String send(String message) {
    //调用方法前可以添加自己的操作
    System.out.println("before method send()");
    smsService.send(message);
    System.out.println("after method send()");
    return message;
  }
}

为什么代理类一定要实现接口?

事实上,代理类不实现接口,上述代码也能正常运行,但是这样就无法保证类型一致性和多态性,违背了面向接口编程的原则,同时也失去了代理的意义

  • 类型一致性:如果SmsProxy不实现SmsService接口,那么在需要SmsService的地方就不能直接使用SmsProxy,这会破坏程序的设计与结构
  • 多态性丧失面向接口编程的一个重要优势是多态性,即同一类型的对象可以有不同的具体行为。如果不实现SmsService接口,就无法利用Java的多态特性,客户端也无法以统一的方式处理SmsService及其代理对象
  • 代理意义:代理模式的核心思想是为其他对象提供一种代理以控制对这个对象的访问。为了做到这一点,代理对象必须能够"冒充"被代理的对象,即它们对外提供的接口应该相同,这样才能在客户端无感知的情况下完成代理功能

JDK 动态代理

JDK动态代理是Java SE自带的一种代理实现方式,主要通过java.lang.reflect.Proxy类以及java.lang.reflect.InvocationHandler接口来实现代理功能。

它能够在运行时动态地生成一个实现了指定接口的新类,这个新类就是代理类,其内部含有指向实际目标对象的引用,并在调用接口方法时执行特定的处理逻辑

1.定义一个接口

2.创建一个实现类实现该接口

3.创建一个实现类实现InvocationHandler接口,这个类将在代理对象的方法被调用时处理具体的逻辑

4.利用Proxy.newInstance()方法生成代理对象,并将自定义的InvocationHandler与实际对象关联起来

java 复制代码
//1.定义一个接口
public interface SmsService {

  String send(String message);

}

//2.创建一个实现类实现该接口
public class SmsServiceImpl implements SmsService {
  @Override
  public String send(String message) {
    //具体的业务逻辑  
    System.out.println("message = " + message);
    return message;
  }
}

//3.创建一个实现类实现`InvocationHandler`接口,这个类将在代理对象的方法被调用时处理具体的逻辑
public class MyInvocationHandler implements InvocationHandler {

  /**
   * 持有代理类中的真实对象
   */
  private final Object target;

  public MyInvocationHandler(Object target) {
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //调用方法之前,可以添加自己的操作
    System.out.println("before method: " + method.getName());
    Object result = method.invoke(target, args);
    //调用方法之后 添加自己的操作
    System.out.println("after method: " + method.getName());
    return result;
  }
}

//4.利用`Proxy.newInstance()`方法生成代理对象,并将自定义的`InvocationHandler`与实际对象关联起来
public class JdkProxyFactory {

  public static Object getProxy(Object target) {
    return Proxy
            .newProxyInstance(
                    target.getClass().getClassLoader(),//目标类的类加载器
                    target.getClass().getInterfaces(),//代理需要实现的接口,可指定多个
                    new MyInvocationHandler(target));//代理对象自定义InvocationHandler
  }

}

@Test
public void test() {
  SmsService real = new SmsServiceImpl();
  SmsService proxy = (SmsService) JdkProxyFactory.getProxy(real);
  //当我们通过代理对象调用接口方法时,实际会触发自定义InvocationHandler中的invoke方法,从而实现在调用前后加入额外的逻辑处理,比如日志记录、事务管理、权限验证等功能
  proxy.send("hello");
}

InvocationHandler中的invoke方法为什么会被自动执行?

接上述代码,使用如下代码生成代理类字节码

java 复制代码
public static void main(String[] args) {
      /**
       * 这个用法是用来指示Java动态代理机制在运行时生成并保存代理类的.class文件到磁盘上。
       * 当Java使用java.lang.reflect.Proxy类生成动态代理时,默认是不会把生成的代理类持久化到文件系统的。
       * 设置这个系统属性可以让JDK的内部类sun.misc.ProxyGenerator在生成代理类字节码时将其写入到特定目录下
       */
      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
      SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
      smsService.send("java");
}

public final class $Proxy0 extends Proxy implements SmsService {
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

  public $Proxy0(InvocationHandler var1) throws  {
    super(var1);
  }

  public final boolean equals(Object var1) throws  {
    try {
      return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
      throw var3;
    } catch (Throwable var4) {
      throw new UndeclaredThrowableException(var4);
    }
  }

  public final String toString() throws  {
    try {
      return (String)super.h.invoke(this, m2, (Object[])null);
    } catch (RuntimeException | Error var2) {
      throw var2;
    } catch (Throwable var3) {
      throw new UndeclaredThrowableException(var3);
    }
  }

  public final String send(String var1) throws  {
    try {
      return (String)super.h.invoke(this, m3, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
      throw var3;
    } catch (Throwable var4) {
      throw new UndeclaredThrowableException(var4);
    }
  }

  public final int hashCode() throws  {
    try {
      return (Integer)super.h.invoke(this, m0, (Object[])null);
    } catch (RuntimeException | Error var2) {
      throw var2;
    } catch (Throwable var3) {
      throw new UndeclaredThrowableException(var3);
    }
  }

  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
      m2 = Class.forName("java.lang.Object").getMethod("toString");
      m3 = Class.forName("com.cmy.interview.day5.theInterface.SmsService").getMethod("send", Class.forName("java.lang.String"));
      m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
      throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
      throw new NoClassDefFoundError(var3.getMessage());
    }
  }
}

上面的代码是代理类的字节码

调用代理类的send()本质上都是在调用super.h.invoke

这里的h是自定义的InvocationHandler实现类

同时传入了代理类本体(this),通过反射获取到的方法m3(send()),以及参数(new Object[]{var1})

可以看到除了重写了本例中SmsServicesend方法,Object的基础方法hashCode(),equals(),toString()都被覆写以及全部重定向给InvocationHandler

当我们通过代理对象调用接口方法时,实际会触发自定义InvocationHandler.invoke()方法,从而实现在调用前后加入额外的逻辑处理,比如日志记录、事务管理、权限验证等功能

CGLIB 动态代理

CGLIB是一个功能强大,高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口 ,解决了JDK动态代理的一个致命问题:只能代理实现了接口的类

通常可以使用 Java 的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB 是一个好的选择

CGLIB通过字节码技术为一个类创建子类,并在子类中方法调用前后加入拦截逻辑,从而实现动态代理。这种方式也被称为继承式代理或者子类代理

net.sf.cglib.proxy.Enhance和接口net.sf.cglib.proxy.MethodInterceptor是使用核心

1.引入依赖

2.定义被代理类

3.创建拦截器并实现MethodInterceptor接口,重写intercept()方法

4.创建并使用代理对象

xml 复制代码
<!--1.引入依赖-->
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <!-- 注意版本号可能会更新 -->
  <version>3.3.0</version>
</dependency>
java 复制代码
//2.定义被代理类
public class AliSmsService{
    public void send(){
        //业务逻辑
    }
}
//3.创建拦截器并实现`MethodInterceptor`接口,重写`intercept()`方法
public class MyMethodInterceptor implements MethodInterceptor {
  /**
   *
   * @param o 被代理的类(需要增强的对象)
   * @param method 被拦截的方法(需要增强的方法)
   * @param objects 方法入参
   * @param methodProxy 用于调用原始方法
   * @return
   * @throws Throwable
   */
  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    //调用方法之前,添加自己的操作
    System.out.println("before method " + method.getName());
    Object object = methodProxy.invokeSuper(o, objects);
    //调用方法之后,添加自己的操作
    System.out.println("after method " + method.getName());
    return object;
  }
}
//4.创建并使用代理对象
public class CglibProxyFactory {

  public static Object getProxy(Class<?> clazz){
    //创建动态代理类创建的入口
    Enhancer enhancer = new Enhancer();
    //设置类加载器
    enhancer.setClassLoader(clazz.getClassLoader());
    //设置父类为被代理类
    enhancer.setSuperclass(clazz);
    //设置回调方法(拦截器)
    enhancer.setCallback(new MyMethodInterceptor());
    //创建代理类(被代理类的子类)
    return enhancer.create();
  }
  
}

@Test
public void testCglibProxy(){
  AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
  alismsService.send("java");
}

CGLIB 动态代理原理

接上述代码,通过以下代码生成代理子类的字节码

java 复制代码
public static void main(String[] args) {
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\cglib");
    AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
    aliSmsService.send("java");
}

代理子类的字节码

java 复制代码
public class AliSmsService$$EnhancerByCGLIB$$9d97d15c extends AliSmsService implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$send$0$Method;
    private static final MethodProxy CGLIB$send$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.cmy.interview.day5.impl.AliSmsService$$EnhancerByCGLIB$$9d97d15c");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$send$0$Method = ReflectUtils.findMethods(new String[]{"send", "(Ljava/lang/String;)Ljava/lang/String;"}, (var1 = Class.forName("com.cmy.interview.day5.impl.AliSmsService")).getDeclaredMethods())[0];
        CGLIB$send$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)Ljava/lang/String;", "send", "CGLIB$send$0");
    }

    final String CGLIB$send$0(String var1) {
        return super.send(var1);
    }

    public final String send(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$send$0$Method, new Object[]{var1}, CGLIB$send$0$Proxy) : super.send(var1);
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

    public final String toString() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

    public final int hashCode() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch (var10000.hashCode()) {
            case -1437300215:
                if (var10000.equals("send(Ljava/lang/String;)Ljava/lang/String;")) {
                    return CGLIB$send$0$Proxy;
                }
                break;
            case -508378822:
                if (var10000.equals("clone()Ljava/lang/Object;")) {
                    return CGLIB$clone$4$Proxy;
                }
                break;
            case 1826985398:
                if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                    return CGLIB$equals$1$Proxy;
                }
                break;
            case 1913648695:
                if (var10000.equals("toString()Ljava/lang/String;")) {
                    return CGLIB$toString$2$Proxy;
                }
                break;
            case 1984935277:
                if (var10000.equals("hashCode()I")) {
                    return CGLIB$hashCode$3$Proxy;
                }
        }

        return null;
    }

    public AliSmsService$$EnhancerByCGLIB$$9d97d15c() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        AliSmsService$$EnhancerByCGLIB$$9d97d15c var1 = (AliSmsService$$EnhancerByCGLIB$$9d97d15c)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        AliSmsService$$EnhancerByCGLIB$$9d97d15c var10000 = new AliSmsService$$EnhancerByCGLIB$$9d97d15c();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        AliSmsService$$EnhancerByCGLIB$$9d97d15c var10000 = new AliSmsService$$EnhancerByCGLIB$$9d97d15c();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        AliSmsService$$EnhancerByCGLIB$$9d97d15c var10000 = new AliSmsService$$EnhancerByCGLIB$$9d97d15c;
        switch (var1.length) {
            case 0:
                var10000.<init>();
                CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
                return var10000;
            default:
                throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch (var1) {
            case 0:
                var10000 = this.CGLIB$CALLBACK_0;
                break;
            default:
                var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch (var1) {
            case 0:
                this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
            default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

CGLIB 动态代理的原理基于Java字节码操作技术,具体步骤如下

1.字节码生成

使用CGLIB进行动态代理时,在运行时分析目标类,并利用ASM库生成一个新的类,这个类是目标类的一个子类(public class AliSmsService$$EnhancerByCGLIB$$9d97d15c extends AliSmsService...),目标类是final类无法代理

2.方法拦截

新生成的子类会重写目标类中所有非final且非private的方法,在这些重写的方法内部,CGLIB实现了方法拦截机制,即当调用代理类的方法时,实际上会调用自定义拦截器中的intercept方法

java 复制代码
//...
public final String send(String var1) {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }
    //调用自定义拦截器中的intercept方法,并传入代理类,方法,参数
    return var10000 != null ? (String)var10000.intercept(this, CGLIB$send$0$Method, new Object[]{var1}, CGLIB$send$0$Proxy) : super.send(var1);
}
//...

3.织入横切逻辑

在intercept()方法中,可以添加额外的业务逻辑(比如事务管理、日志记录、权限检查等),这就是所谓的"横切逻辑"。在这个方法内,可以通过回调方法参数获取到方法调用的信息,如方法对象(MethodProxy)、被代理的对象以及方法参数数组,然后决定是否执行原方法或者在前后执行其他操作。

CGLIB 动态代理快在哪里?

在上面生成的字节码文件中,除了代理类字节码之外,还有带FastClass字样的其他两个字节码文件

CGLIB通过创建FastClass对象来加速方法调用,FastClass对象包含了一个方法索引表,可以直接调用方法对应的索引而不是每次都通过反射,从而提高性能

总结

静态代理、动态代理(包括JDK动态代理和CGLIB动态代理)是Java编程中实现代理设计模式的不同方式,它们各有特点及适用场景。

  1. 静态代理
    • 定义:在编译阶段就确定了代理类的形式,代理类和被代理类通常都需要实现同一个接口或继承相同的父类。
    • 优点:代码结构清晰,易于理解。
    • 缺点:每次新增业务类时都需要编写对应的代理类,代码量较大且不易于维护;扩展性差,如果接口增加方法,所有相关的代理类都需要同步更新。
  2. JDK动态代理
    • 定义:基于Java反射机制实现,在运行时动态生成代理类的字节码文件,代理类实现了与被代理类所实现的所有接口。
    • 优点
      • 动态生成,不需要预先写好代理类代码,更灵活。
      • 只要接口不变,代理逻辑可以统一处理,减少重复代码。
    • 限制
      • 被代理的目标类必须实现至少一个接口,对于未实现接口的类无法进行代理。
      • 基于反射,性能有所损耗
  3. CGLIB动态代理
    • 定义:CGLIB通过字节码技术在运行时生成被代理类的子类,从而实现代理功能。
    • 优点
      • 不依赖于接口,能够代理没有实现接口的普通类。
      • 拦截范围较广,除了接口方法,还可以代理类的非final方法。
      • CGLIB通过创建FastClass对象来加速方法调用,方法调用密集情况下,性能较好
    • 缺点
      • 由于是生成子类的方式,所以无法代理final类和final方法。
相关推荐
matrixlzp6 小时前
Java 责任链模式 减少 if else 实战案例
java·设计模式
编程、小哥哥8 小时前
设计模式之组合模式(营销差异化人群发券,决策树引擎搭建场景)
决策树·设计模式·组合模式
hxj..9 小时前
【设计模式】外观模式
java·设计模式·外观模式
吾与谁归in9 小时前
【C#设计模式(10)——装饰器模式(Decorator Pattern)】
设计模式·c#·装饰器模式
无敌岩雀11 小时前
C++设计模式行为模式———命令模式
c++·设计模式·命令模式
In_life 在生活20 小时前
设计模式(四)装饰器模式与命令模式
设计模式
瞎姬霸爱.21 小时前
设计模式-七个基本原则之一-接口隔离原则 + SpringBoot案例
设计模式·接口隔离原则
鬣主任1 天前
Spring设计模式
java·spring boot·设计模式
程序员小海绵【vincewm】1 天前
【设计模式】结合Tomcat源码,分析外观模式/门面模式的特性和应用场景
设计模式·tomcat·源码·外观模式·1024程序员节·门面模式
丶白泽1 天前
重修设计模式-行为型-命令模式
设计模式·命令模式