一,结构性模式介绍

结构型模式描述如何将对象按某种布局组成更大的结构。它分为类结构型模式对象结构型模式,前者采用继承机制来组织接口和类,
后者采用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构性模式分为七种:代理模式,适配器模式,装饰者模式,桥接模式,外观模式,组合模式,享元模式。

二,代理模式

2.1 什么是代理模式?

核心思想:为一个对象提供一个替身或占位符,以控制对这个对象的访问。

通俗理解:就像明星和经纪人的关系。你想请明星演出,你不会直接联系明星,而是先联系他的经纪人。经纪人会处理一些前置工作(谈合同、排档期)和后置工作(收钱、善后),在合适的时机才会让明星本人出场。这个“经纪人”就是明星的“代理”。

作用:通过引入代理对象,可以在不改变原始对象(目标对象)的情况下,扩展其功能。主要功能包括:权限控制、延迟加载、日志记录、性能监控、事务管理等。

Java 中实现代理主要有三种:静态代理基于 JDK 的动态代理基于 CGLib 的动态代理

2.2 静态代理

这种方式是“硬编码”的,在编译期就确定了代理关系。

角色

  1. 抽象主题(Subject):通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject):实现了抽象主题的具体类,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理类(Proxy):也实现了抽象主题,它包含对真实主题的引用,可以访问、控制或扩展真实主题的功能。

代码示例

// 1. 抽象主题接口
interface Star {
    void perform();
}

// 2. 真实主题 - 周杰伦
class JayChou implements Star {
    @Override
    public void perform() {
        System.out.println("周杰伦在唱歌...");
    }
}

// 3. 代理类 - 经纪人
class Agent implements Star {
    // 持有真实对象的引用
    private Star target;

    public Agent(Star target) {
        this.target = target;
    }

    @Override
    public void perform() {
        System.out.println("经纪人谈合同、安排档期..."); // 前置增强
        target.perform(); // 调用真实对象的方法
        System.out.println("经纪人收钱、处理事后工作..."); // 后置增强
    }
}

// 4. 客户端使用
public class StaticProxyDemo {
    public static void main(String[] args) {
        Star jay = new JayChou(); // 创建真实对象
        Star agent = new Agent(jay); // 创建代理对象,传入真实对象
        agent.perform(); // 你接触的是代理对象,但实际干活的是真实对象
    }
}

输出结果:

经纪人谈合同、安排档期...
周杰伦在唱歌...
经纪人收钱、处理事后工作...

静态代理的优缺点:

  • 优点:简单、直观,无需反射,性能较好。
  • 缺点
    • 如果接口增加方法,代理类和真实类都需要实现,违反了“开闭原则”
    • 每个真实类都需要创建一个对应的代理类,当真实类很多时,会导致类数量爆炸,难以维护

2.3 JDK动态代理

JDK 动态代理是 Java 标准库提供的,在运行时动态生成代理类的机制。它基于接口来实现代理,不需要我们手动编写具体的代理类。

核心:在运行时动态创建接口的实现类

核心组件

要理解 JDK 动态代理,需要掌握三个核心概念:

  • InvocationHandler 接口:代理对象的调用处理器
  • Proxy:用于创建代理实例的工具类
  • 目标对象:被代理的原始对象

代码示例

  1. 定义接口

    // 用户服务接口
    public interface UserService {
        void addUser(String username);
        void deleteUser(String username);
        String getUser(String username);
    }
    
  2. 实现接口(目标对象)

    // 用户服务实现类 - 这是我们要代理的真实对象
    public class UserServiceImpl implements UserService {
        @Override
        public void addUser(String username) {
            System.out.println("添加用户: " + username);
            // 模拟业务逻辑
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        @Override
        public void deleteUser(String username) {
            System.out.println("删除用户: " + username);
        }
        
        @Override
        public String getUser(String username) {
            System.out.println("查询用户: " + username);
            return "用户信息: " + username;
        }
    }
    
  3. 实现调用处理器

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class LoggingHandler implements InvocationHandler {
        // 持有被代理对象的引用
        private final Object target;
        
        public LoggingHandler(Object target) {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 前置增强:记录方法开始时间
            long startTime = System.currentTimeMillis();
            System.out.println("【开始执行】方法: " + method.getName() + 
                              ", 参数: " + Arrays.toString(args));
            
            try {
                // 调用真实对象的方法
                Object result = method.invoke(target, args);
                
                // 后置增强:记录方法执行结果
                System.out.println("【执行成功】方法: " + method.getName() + 
                                  ", 返回值: " + result);
                return result;
                
            } catch (Exception e) {
                // 异常增强:记录异常信息
                System.out.println("【执行失败】方法: " + method.getName() + 
                                  ", 异常: " + e.getMessage());
                throw e;
            } finally {
                // 最终增强:记录方法执行时间
                long endTime = System.currentTimeMillis();
                System.out.println("【执行结束】方法: " + method.getName() + 
                                  ", 耗时: " + (endTime - startTime) + "ms");
                System.out.println("-----------------------------------");
            }
        }
    }
    
  4. 创建代理对象并使用

    import java.lang.reflect.Proxy;
    
    public class DynamicProxyDemo {
        public static void main(String[] args) {
            // 创建真实对象
            UserService realService = new UserServiceImpl();
            
            // 创建调用处理器
            InvocationHandler handler = new LoggingHandler(realService);
            
            // 创建代理对象
            UserService proxy = (UserService) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(), // 类加载器
                realService.getClass().getInterfaces(),  // 接口数组
                handler                                  // 调用处理器
            );
            
            // 使用代理对象
            System.out.println("代理对象类型: " + proxy.getClass().getName());
            System.out.println("===================================");
            
            // 调用方法 - 这些调用都会被代理拦截
            proxy.addUser("张三");
            proxy.getUser("张三");
            proxy.deleteUser("李四");
            
            // 验证代理对象确实实现了接口
            System.out.println("是否是UserService实例: " + (proxy instanceof UserService));
        }
    }
    
  5. 运行结果分析

    运行上面的代码,你会看到类似这样的输出

    代理对象类型: com.sun.proxy.$Proxy0
    ===================================
    【开始执行】方法: addUser, 参数: [张三]
    添加用户: 张三
    【执行成功】方法: addUser, 返回值: null
    【执行结束】方法: addUser, 耗时: 105ms
    -----------------------------------
    【开始执行】方法: getUser, 参数: [张三]
    查询用户: 张三
    【执行成功】方法: getUser, 返回值: 用户信息: 张三
    【执行结束】方法: getUser, 耗时: 0ms
    -----------------------------------
    【开始执行】方法: deleteUser, 参数: [李四]
    删除用户: 李四
    【执行成功】方法: deleteUser, 返回值: null
    【执行结束】方法: deleteUser, 耗时: 0ms
    -----------------------------------
    是否是UserService实例: true
    
  6. 核心原理

    Proxy.newProxyInstance() 方法参数:

    • 类加载器:用于加载动态生成的代理类
    • 接口数组:代理类要实现的接口
    • InvocationHandler:方法调用的处理逻辑

    InvocationHandler.invoke() 方法参数:

    • proxy:代理对象本身(通常不需要使用)
    • method:被调用的方法
    • args:方法参数
  7. 与静态代理的区别

    方面静态代理JDK动态代理
    创建时机编译期运行期
    实现方式代理类需要显式定义,实现与目标类相同的接口。通过 java.lang.reflect.Proxy 类的 newProxyInstance 方法动态生成。
    源码存在性有实际的 .java 源文件和 .class 字节码文件。没有实际的 .java 文件,其 .class 字节码是在运行时动态生成,并直接加载到JVM中。
    灵活性。每个代理类只能为一个接口服务。如果有多个接口需要代理,需要编写大量的代理类。。一个 InvocationHandler 可以代理多种不同类型的接口,通用性强。
    维护性。如果接口增加方法,目标类和代理类都需要同步修改。。接口增加方法,InvocationHandlerinvoke 方法不需要修改,它会自动代理所有接口方法。
    依赖关系代理类直接依赖目标对象。代理类依赖 InvocationHandler 对象,由该 handler 去调用目标对象。
    技术要求仅需基本的Java面向对象知识。需要理解反射机制和动态代理的API。

2.4 CGLib动态代理

因为 JDK 动态代理只能基于接口,所以后来出现了 CGLib 这样的基于继承的动态代理库,它可以代理未实现任何接口的普通类。

Spring AOP 默认策略是:如果目标对象实现了接口,则使用 JDK 动态代理;如果没有,则使用 CGLib。


CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它通过继承目标类并在运行时生成子类来实现代理。这意味着它可以代理没有实现接口的普通类

CGLIB vs JDK 动态代理

特性JDK动态代理CGLIB代理
代理方式实现接口继承类
目标要求必须实现接口可以是普通类
性能较慢(反射调用)较快(方法调用)
限制不能代理类不能代理final类/方法,不能代理private方法
依赖JDK自带需要额外jar包

环境准备

首先需要添加 CGLIB 依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

完整代码示例

  1. 创建目标类(不需要接口)

    // 用户服务类 - 没有实现任何接口!
    public class UserService {
        public void addUser(String username) {
            System.out.println("添加用户: " + username);
            // 模拟业务逻辑
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public void deleteUser(String username) {
            System.out.println("删除用户: " + username);
        }
        
        public String getUser(String username) {
            System.out.println("查询用户: " + username);
            return "用户信息: " + username;
        }
        
        // final方法 - CGLIB无法代理
        public final void finalMethod() {
            System.out.println("这是final方法");
        }
        
        // private方法 - CGLIB不会代理
        private void privateMethod() {
            System.out.println("这是private方法");
        }
    }
    
  2. 实现方法拦截器

    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class LoggingInterceptor implements MethodInterceptor {
        /**
         * @param obj 代理对象(增强后的对象)
         * @param method 被拦截的方法
         * @param args 方法参数
         * @param proxy 用于调用父类(原始)方法
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            // 前置增强
            long startTime = System.currentTimeMillis();
            System.out.println("【CGLIB代理】开始执行方法: " + method.getName() + 
                              ", 参数: " + Arrays.toString(args));
            
            try {
                // 调用原始方法 - 注意这里使用MethodProxy而不是Method
                Object result = proxy.invokeSuper(obj, args);
                
                // 后置增强
                System.out.println("【CGLIB代理】执行成功: " + method.getName() + 
                                  ", 返回值: " + result);
                return result;
                
            } catch (Exception e) {
                // 异常增强
                System.out.println("【CGLIB代理】执行失败: " + method.getName() + 
                                  ", 异常: " + e.getMessage());
                throw e;
            } finally {
                // 最终增强
                long endTime = System.currentTimeMillis();
                System.out.println("【CGLIB代理】执行结束: " + method.getName() + 
                                  ", 耗时: " + (endTime - startTime) + "ms");
                System.out.println("-----------------------------------");
            }
        }
    }
    
  3. 创建代理对象并使用

    import net.sf.cglib.proxy.Enhancer;
    
    public class CglibProxyDemo {
        public static void main(String[] args) {
            // 创建Enhancer对象 - CGLIB的核心类
            Enhancer enhancer = new Enhancer();
            
            // 设置父类(被代理的类)
            enhancer.setSuperclass(UserService.class);
            
            // 设置回调(方法拦截器)
            enhancer.setCallback(new LoggingInterceptor());
            
            // 创建代理对象
            UserService proxy = (UserService) enhancer.create();
            
            // 使用代理对象
            System.out.println("代理对象类型: " + proxy.getClass().getName());
            System.out.println("父类: " + proxy.getClass().getSuperclass().getName());
            System.out.println("===================================");
            
            // 调用方法
            proxy.addUser("王五");
            proxy.getUser("王五");
            proxy.deleteUser("赵六");
            
            // 测试final方法
            proxy.finalMethod();
            
            // 验证继承关系
            System.out.println("是否是UserService子类: " + 
                              (proxy instanceof UserService));
        }
    }
    
  4. 运行结果分析

    代理对象类型: com.example.UserService$$EnhancerByCGLIB$$12345678
    父类: com.example.UserService
    ===================================
    【CGLIB代理】开始执行方法: addUser, 参数: [王五]
    添加用户: 王五
    【CGLIB代理】执行成功: addUser, 返回值: null
    【CGLIB代理】执行结束: addUser, 耗时: 105ms
    -----------------------------------
    【CGLIB代理】开始执行方法: getUser, 参数: [王五]
    查询用户: 王五
    【CGLIB代理】执行成功: getUser, 返回值: 用户信息: 王五
    【CGLIB代理】执行结束: getUser, 耗时: 0ms
    -----------------------------------
    【CGLIB代理】开始执行方法: deleteUser, 参数: [赵六]
    删除用户: 赵六
    【CGLIB代理】执行成功: deleteUser, 返回值: null
    【CGLIB代理】执行结束: deleteUser, 耗时: 0ms
    -----------------------------------
    这是final方法
    是否是UserService子类: true
    

CGLIB 有以下限制:

  • 不能代理final类
  • 不能代理final方法
  • 不能代理private方法(但可以通过配置改变)
  • 需要额外依赖
  • 构造方法不会被拦截

这些不能代理的原因是因为,通过的是继承实现的代理,而 java 的继承无法重写 private 方法,无法重写 final 方法

CGLIB 代理的核心要点:

  1. 基于继承:通过生成目标类的子类来实现代理
  2. 使用Enhancer:核心工具类,用于创建代理对象
  3. MethodInterceptor:方法拦截器,类似JDK的InvocationHandler
  4. MethodProxy.invokeSuper():调用原始方法的高效方式
  5. 适用场景:代理没有接口的类,性能要求较高的场景

CGLIB 在 Spring、Hibernate 等主流框架中广泛应用,特别是在需要代理普通类的场景下。

2.5 三种代理对比

  1. 静态代理(Static Proxy)

    // 1. 定义接口
    public interface UserService {
        void addUser(String username);
    }
    
    // 2. 真实实现类
    public class UserServiceImpl implements UserService {
        @Override
        public void addUser(String username) {
            System.out.println("添加用户: " + username);
        }
    }
    
    // 3. 代理类(手动编写)
    public class UserServiceProxy implements UserService {
        private UserService target; // 持有真实对象的引用
        
        public UserServiceProxy(UserService target) {
            this.target = target;
        }
        
        @Override
        public void addUser(String username) {
            // 前置增强
            System.out.println("开始事务...");
            long start = System.currentTimeMillis();
            
            // 调用真实方法
            target.addUser(username);
            
            // 后置增强
            long end = System.currentTimeMillis();
            System.out.println("提交事务...");
            System.out.println("耗时: " + (end - start) + "ms");
        }
    }
    
    // 4. 使用
    public class StaticProxyDemo {
        public static void main(String[] args) {
            UserService realService = new UserServiceImpl();
            UserService proxy = new UserServiceProxy(realService);
            proxy.addUser("张三");
        }
    }
    

    原理分析

    • 编译时生成:代理类在编译前就已经存在
    • 手动编码:需要为每个被代理的类编写对应的代理类
    • 继承/实现关系:代理类和目标类实现相同的接口
  2. JDK 动态代理(JDK Dynamic Proxy)

    底层原理

    // 核心原理模拟 - Spring内部的简化实现
    public class JdkProxySimulator {
        
        /**
         * 模拟Proxy.newProxyInstance的内部实现
         */
        public static Object createProxy(Object target) throws Exception {
            // 1. 生成代理类的字节码
            byte[] proxyClassBytes = generateProxyClass(target);
            
            // 2. 定义类
            Class<?> proxyClass = defineClass(proxyClassBytes);
            
            // 3. 创建实例
            Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
            return constructor.newInstance(new LoggingHandler(target));
        }
        
        /**
         * 模拟生成的代理类(实际由JDK动态生成)
         */
        public class $Proxy0 extends Proxy implements UserService {
            private static Method m1; // addUser方法
            
            static {
                try {
                    m1 = UserService.class.getMethod("addUser", String.class);
                } catch (NoSuchMethodException e) {
                    throw new Error(e);
                }
            }
            
            public $Proxy0(InvocationHandler h) {
                super(h);
            }
            
            @Override
            public void addUser(String username) {
                try {
                    // 调用InvocationHandler的invoke方法
                    h.invoke(this, m1, new Object[]{username});
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    // 实际使用
    public class JdkProxyDemo {
        public static void main(String[] args) {
            UserService realService = new UserServiceImpl();
            
            UserService proxy = (UserService) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(),
                new Class[]{UserService.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("Before method: " + method.getName());
                        Object result = method.invoke(realService, args);
                        System.out.println("After method: " + method.getName());
                        return result;
                    }
                }
            );
            
            proxy.addUser("李四");
        }
    }
    

    字节码层面分析

    // 生成的代理类反编译结果大致如下:
    public final class $Proxy1 extends Proxy implements UserService {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
        
        public $Proxy1(InvocationHandler var1) {
            super(var1);
        }
        
        public final void addUser(String var1) {
            try {
                // 关键:调用InvocationHandler的invoke方法
                super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
        
        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.example.UserService").getMethod("addUser", 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());
            }
        }
    }
    
  3. CGLIB 动态代理(CGLIB Dynamic Proxy)

 **底层原理**
 ```java
 // CGLIB核心原理模拟
 public class CglibSimulator {
     
     /**
      * 模拟Enhancer.create()的内部实现
      */
     public static Object createProxy(Class<?> superclass) {
         // 1. 创建字节码生成器
         ClassGenerator generator = new ClassGenerator();
         
         // 2. 设置父类
         generator.setSuperClass(superclass);
         
         // 3. 添加方法拦截器
         generator.addMethodInterceptor("addUser", new MethodInterceptor() {
             public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                 System.out.println("Before: " + method.getName());
                 Object result = proxy.invokeSuper(obj, args);
                 System.out.println("After: " + method.getName());
                 return result;
             }
         });
         
         // 4. 生成字节码并创建实例
         return generator.create();
     }
 }
 
 // 实际生成的CGLIB代理类大致如下:
 public class UserService$$EnhancerByCGLIB$$123456 extends UserService {
     private MethodInterceptor interceptor;
     private static final Method CGLIB$addUser$0$Method;
     private static final MethodProxy CGLIB$addUser$0$Proxy;
     
     static {
         try {
             // 获取目标方法
             CGLIB$addUser$0$Method = UserService.class.getMethod("addUser", String.class);
             // 创建方法代理(FastClass机制)
             CGLIB$addUser$0$Proxy = MethodProxy.create(
                 UserService.class, 
                 UserService$$EnhancerByCGLIB$$123456.class, 
                 "()V", "addUser", "CGLIB$addUser$0"
             );
         } catch (NoSuchMethodException e) {
             throw new Error(e);
         }
     }
     
     // 重写父类方法
     public final void addUser(String username) {
         MethodInterceptor tmp = this.interceptor;
         if (tmp != null) {
             // 调用拦截器
             tmp.intercept(this, CGLIB$addUser$0$Method, new Object[]{username}, CGLIB$addUser$0$Proxy);
         } else {
             // 直接调用父类方法
             super.addUser(username);
         }
     }
     
     // FastClass直接调用(绕过反射)
     public final void CGLIB$addUser$0(String username) {
         super.addUser(username);
     }
 }
 ```

 **FastClass机制原理**

 **核心原理**:**FastClass通过方法索引来直接调用,避免反射查找的开销**。它为每个方法分配一个唯一的索引,通过索引直接定位并调用方法。

 ```java
 // FastClass模拟 - 避免反射调用开销
 public class UserServiceFastClass {
     private static final int INDEX_addUser = 0;
     private static final int INDEX_getUser = 1;
     
     /**
      * 通过索引直接调用方法,避免反射
      */
     public Object invoke(int index, Object obj, Object[] args) {
         switch (index) {
             case INDEX_addUser:
                 ((UserService) obj).addUser((String) args[0]);
                 return null;
             case INDEX_getUser:
                 return ((UserService) obj).getUser((String) args[0]);
             default:
                 throw new IllegalArgumentException("Invalid method index");
         }
     }
 }
 ```

4. 三种代理的详细对比

 **原理对比表**

 | 特性           | 静态代理     | JDK动态代理  | CGLIB动态代理     |
 | :------------- | :----------- | :----------- | :---------------- |
 | **生成时机**   | 编译时       | 运行时       | 运行时            |
 | **生成方式**   | 手动编码     | Proxy类生成  | Enhancer生成      |
 | **字节码操作** | 无           | 有           | 有                |
 | **实现机制**   | 实现相同接口 | 实现相同接口 | 继承目标类        |
 | **方法调用**   | 直接调用     | 反射调用     | FastClass直接调用 |

5. 三种代理的核心区别:

 1.  **静态代理**:编译时确定,手动编码,控制精确但繁琐
 2.  **JDK动态代理**:基于接口,运行时生成,使用反射调用
 3.  **CGLIB代理**:基于继承,运行时生成,使用**FastClass**优化

6. 现代开发建议

 -   Spring Boot项目通常使用**CGLIB**作为默认代理

 -   框架开发优先考虑JDK动态代理(面向接口)

 -   特殊场景考虑静态代理(需要精确控制时)

 -   理解原理有助于解决事务失效等典型问题

2.6 FastClass详解

提问:为什么方法索引能避免反射并加快速度?

2.6.1 反射的瓶颈在哪里?

反射调用的详细步骤:

public class ReflectionSlowSteps {
    public static void main(String[] args) throws Exception {
        UserService service = new UserService();
        Method method = UserService.class.getMethod("getUser", String.class);
        
        // 看似简单的一行反射调用,背后发生了:
        method.invoke(service, "123");
    }
}

反射调用背后的复杂流程:

  1. 方法查找:通过方法名和参数类型查找Method对象
  2. 权限检查:检查调用者是否有访问该方法的权限
  3. 参数装箱:将基本类型参数装箱为Object
  4. 类型转换:验证参数类型是否匹配
  5. 方法分派:通过JNI调用本地方法
  6. 实际调用:最终调用目标方法
  7. 返回值处理:拆箱和类型转换

2.6.2 FastClass如何解决这些问题?

FastClass 的工作机制:

// 假设我们有一个目标类
public class UserService {
    public String getUser(String id) { return "user_" + id; }
    public void updateUser(String id) { /* 更新逻辑 */ }
}

// FastClass为这个类生成"方法索引表"
public class UserServiceFastClass extends FastClass {
    // 关键:编译时确定的方法索引
    private static final int INDEX_getUser = 0;
    private static final int INDEX_updateUser = 1;
    
    //  方法签名到索引的映射表(编译时生成)
    private static final Map<String, Integer> METHOD_INDEX_MAP = Map.of(
        "getUser(String)": 0,
        "updateUser(String)": 1
    );
    
    /**
     *  通过索引直接调用 - 避免反射!
     */
    @Override
    public Object invoke(int index, Object obj, Object[] args) {
        UserService target = (UserService) obj;
        switch (index) {  // JVM对switch有深度优化
            case INDEX_getUser:
                return target.getUser((String) args[0]);  // 直接方法调用!
            case INDEX_updateUser:
                target.updateUser((String) args[0]);     // 直接方法调用!
                return null;
            default:
                throw new IllegalArgumentException("Invalid index: " + index);
        }
    }
    
    /**
     *  获取方法索引(只需调用一次)
     */
    public int getIndex(String methodName, Class[] paramTypes) {
        String key = methodName + "(" + Arrays.stream(paramTypes)
                                           .map(Class::getSimpleName)
                                           .collect(Collectors.joining(",")) + ")";
        return METHOD_INDEX_MAP.getOrDefault(key, -1);
    }
}

2.6.3 详细步骤对比

public class PerformanceComparison {
    
    public void reflectionCall(Object target, Method method, Object[] args) throws Exception {
        //  反射调用 - 慢路径
        method.invoke(target, args);
        // 背后发生:
        // 1. 检查methodAccessor是否存在
        // 2. 验证参数数量和类型
        // 3. 进行权限检查  
        // 4. 通过JNI调用本地方法
        // 5. 本地方法中查找实际方法地址
        // 6. 调用实际方法
    }
    
    public void fastClassCall(Object target, FastClass fastClass, int methodIndex, Object[] args) {
        //  FastClass调用 - 快路径
        fastClass.invoke(methodIndex, target, args);
        // 背后发生:
        // 1. 直接switch到对应case(JVM优化为跳转表)
        // 2. 直接调用目标方法(就像手写代码一样)
    }
}

2.6.4 总结

为什么方法索引能大幅提升性能:

  1. 避免方法查找 - 索引直接对应方法,无需名称查找
  2. 避免权限检查 - 生成时已验证,运行时直接调用
  3. 避免反射机制 - 直接方法调用 vs 反射的JNI调用
  4. 利于JIT优化 - switch语句可以被深度优化
  5. 减少调用层级 - 从多层间接调用变为直接调用

本质上是:空间换时间的思想,通过预生成方法索引映射表,将运行时的动态查找转换为编译时的静态映射,从而避免了反射的各种运行时开销。

三,适配器模式

定义:将一个类的接口转换成客户希望的外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为类适配器模式对象适配器模式,前者类之间的耦合度比后者高,且要求了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

3.1 结构

适配器模式 (Adapter) 包含以下主要角色:

  • **目标(target)接口:**当前系统业务所期待的接口,它可以是抽象类或接口,即你想要的接口
  • **适配者(Adapter)类:**它是被访问和适配的现存组件库中的组件接口,即旧的当前需要转换的类
  • 适配器(Adapter)类:它是一个转换器,通过继承引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访
    问适配者。

生活化比喻:出国旅行充电器

想象一下,你从中国(当前系统)日本(现存组件库) 旅行。

  • 你的中国国标插头充电器(目标接口):这是你习惯使用的,也是你的手机期待插入的接口。它有两个扁平的插脚。
  • 日本的墙壁插座(适配者类):这是日本本地存在的、提供电力的组件。它有两个扁平的插孔,但没有接地孔,形状和中国的不同。
  • 电源转换插头(适配器类):这是一个小装置,一头是符合日本插座标准的插脚,另一头是提供中国标准插孔的接口。

3.2 代码实现

  1. 目标接口 - ChinaPowerOutlet

    这是你的中国设备所“期待”的接口。它定义了“供电”这个方法。你的手机只知道要通过这个接口来充电。

    // 目标接口:中国标准电源接口
    public interface ChinaPowerOutlet {
        // 提供电力,返回电压(比如220V)
        String providePower();
    }
    
  2. 适配者类 - JapanesePowerSource

    这是在日本已经存在的、能正常工作的组件。它有自己的供电方式,但它的接口(插孔形状)不符合你的中国设备的要求。

    // 适配者类:日本标准的电源
    public class JapanesePowerSource {
        // 日本电源的原始方法,接口不同
        public String supplyPower() {
            return "100V 交流电";
        }
    }
    

    注意:它的方法名是 supplyPower(),而不是目标接口的 providePower()

  3. 适配器类 - PowerAdapter

 适配器的作用就是**站在中间做翻译和转换工作**。它需要实现目标接口,这样你的中国设备才能把它当成一个“中国插座”来使用。同时,它内部必须持有一个**对“日本电源”的引用**,以便调用其真正的功能。
 有两种方式实现适配器:**类适配器(继承)** 和**对象适配器(组合)**,更推荐使用的是**对象适配器**,因为它更灵活。

 -   **类适配器**

     通过继承 - 需要多重继承,Java中不直接支持,但可通过继承类并实现接口来模拟

     ```java
     // 适配器类:电源转换器 (类适配器模式 - 继承方式)
     // 它继承了被适配者,同时实现了目标接口
     public class ClassPowerAdapter extends JapanesePowerSource implements ChinaPowerOutlet {
     
         @Override
         public String providePower() {
             // 直接调用父类(被适配者)的方法
             String powerFromSuper = super.supplyPower();
             return powerFromSuper + " (通过类适配器转换)";
         }
     }
     ```

 -   **对象适配器**

     ```java
     // 适配器类:电源转换器 (对象适配器模式 - 组合方式)
     public class PowerAdapter implements ChinaPowerOutlet {
     
         // 持有适配者对象的引用
         private JapanesePowerSource japanesePowerSource;
     
         // 通过构造器传入需要被适配的对象
         public PowerAdapter(JapanesePowerSource japanesePowerSource) {
             this.japanesePowerSource = japanesePowerSource;
         }
     
         // 实现目标接口的方法
         @Override
         public String providePower() {
             // 在内部调用适配者对象的方法,进行转换
             String japanesePower = japanesePowerSource.supplyPower();
             // 这里可能还有一些适配逻辑,比如电压转换(为了简单,我们只返回结果)
             // 实际上,适配器可能还会包含一个物理的变压器来转换电压
             return japanesePower + " (通过适配器转换后)";
         }
     }
     ```

 4.   **客户端使用**

      ```java
      public class Traveler {
          public static void main(String[] args) {
              // 1. 你在日本找到了一个墙壁插座(现有的、不兼容的组件)
              JapanesePowerSource japaneseOutlet = new JapanesePowerSource();
      
              // 2. 你把电源转换器(适配器)插到日本插座上
              //    并将日本插座“适配”成了中国插座
              ChinaPowerOutlet adapter = new PowerAdapter(japaneseOutlet);
      
              // 3. 现在,你可以把你的中国插头充电器插入这个适配器了!
              //    对于你的充电器来说,它感觉就像插在中国的插座上一样。
              chargeMyPhone(adapter);
          }
      
          // 你的手机充电方法,它只认识中国的电源接口
          public static void chargeMyPhone(ChinaPowerOutlet outlet) {
              String power = outlet.providePower();
              System.out.println("手机正在充电,电源信息:" + power);
          }
      }
      ```

 5.   **输出结果**

      ```txt
      手机正在充电,电源信息:100V 交流电 (通过适配器转换后)
      ```

 **最关键的理解点:**

 -   **适配器** 必须 **实现/继承** **目标接口**,这样客户端才能以统一的方式使用它。
 -   **适配器** 内部必须 **知道** **适配者**(通过组合或继承),这样才能把客户端的请求**委托**给适配者去真正执行。

 这样一来,客户端就完全不需要知道`JapanesePowerSource`的存在,它只和熟悉的`ChinaPowerOutlet`打交道,实现了**解耦**。这就是适配器模式的魅力所在。

逻辑链路:

新系统接口 ← 适配器类(实现) ← 引用老接口 ← 转换调用

用适配器类去实现新接口,然后在适配器类里面引用老的接口,在里面去将老的接口转换成新的接口

3.3 使用场景

  1. . 系统升级兼容

    • 新系统要用老系统的功能
    • 但老系统的接口不符合新标准
    • 例子:新日志框架兼容老日志代码
  2. 集成第三方库

    • 项目要用多个第三方库

    • 它们功能相似但接口不同

    • 例子:同时接入支付宝、微信支付,但接口不一样

  3. 复用遗留代码

    • 老系统代码还能用,但接口太老旧

    • 不想重写,只想包装一下

    • 例子:10 年前的用户服务类,现在还想用

  4. 统一接口标准

    • 多个类似组件接口不统一

    • 想让他们提供一致的调用方式

    • 例子:不同格式的数据转换器 (XML、JSON、CSV)

核心价值:

  • 不改老代码,让新系统能用老功能,像个"转接头"一样工作。
  • 简单说就是:老代码能用但接口不对,适配器来当翻译官

3.4 场景代码举例

3.4.1 案例1

场景分析

  • 4.0系统接口5.0系统接口:功能相同但参数、实现不同
  • 集成平台:想要统一的鉴权 + 统一入参
  • 目标:用一套代码调用两个不同版本的接口

代码举例

  1. . 定义统一的目标接口

    // 集成平台统一的接口标准
    public interface UnifiedApi {
        ApiResponse execute(ApiRequest request);
    }
    
    // 统一请求参数
    public class ApiRequest {
        private String token;        // 统一鉴权token
        private String method;       // 方法名:getUser、createOrder等
        private Map<String, Object> params; // 统一参数格式
        private String version;      // 指定调用哪个版本:"4.0" 或 "5.0"
        
        // 构造方法、getter、setter...
    }
    
    // 统一响应格式
    public class ApiResponse {
        private boolean success;
        private String message;
        private Object data;
        private String version;      // 标识来自哪个版本
        
        // 构造方法、getter、setter...
    }
    
  2. 现有的两个系统接口(不兼容的)

    // 4.0系统接口 - 老系统
    public class System4Api {
        // 4.0系统的鉴权和调用方式
        public Map<String, Object> callV4(String authKey, String action, 
                                        Map<String, String> inputParams) {
            // 4.0系统的具体实现
            Map<String, Object> result = new HashMap<>();
            result.put("code", 200);
            result.put("msg", "4.0系统响应");
            result.put("v4_data", "处理结果: " + inputParams);
            return result;
        }
    }
    
    // 5.0系统接口 - 新系统  
    public class System5Api {
        // 5.0系统的鉴权和调用方式(完全不同)
        public System5Response invokeV5(System5Request request) {
            // 5.0系统的具体实现
            System5Response response = new System5Response();
            response.setStatus(0);
            response.setMessage("5.0系统响应");
            response.setPayload("处理结果: " + request.getParameters());
            return response;
        }
    }
    
    // 5.0系统的请求类
    class System5Request {
        private String accessToken;
        private String operation;
        private Map<String, Object> parameters;
        // getter、setter...
    }
    
    // 5.0系统的响应类  
    class System5Response {
        private int status;
        private String message;
        private Object payload;
        // getter、setter...
    }
    
  3. 创建适配器(核心)

    // 4.0系统适配器
    public class System4Adapter implements UnifiedApi {
        private System4Api system4Api;
        
        public System4Adapter(System4Api system4Api) {
            this.system4Api = system4Api;
        }
        
        @Override
        public ApiResponse execute(ApiRequest request) {
            // 统一的鉴权验证
            if (!authenticate(request.getToken())) {
                return ApiResponse.fail("鉴权失败");
            }
            
            // 将统一参数转换成4.0系统需要的格式
            Map<String, String> v4Params = convertToV4Params(request.getParams());
            
            // 调用4.0系统
            Map<String, Object> v4Result = system4Api.callV4(
                extractV4AuthKey(request.getToken()), // 从统一token提取4.0的authKey
                request.getMethod(),
                v4Params
            );
            
            // 将4.0系统的响应转换成统一格式
            return convertToUnifiedResponse(v4Result, "4.0");
        }
        
        private Map<String, String> convertToV4Params(Map<String, Object> unifiedParams) {
            Map<String, String> v4Params = new HashMap<>();
            for (Map.Entry<String, Object> entry : unifiedParams.entrySet()) {
                v4Params.put(entry.getKey(), String.valueOf(entry.getValue()));
            }
            return v4Params;
        }
        
        private ApiResponse convertToUnifiedResponse(Map<String, Object> v4Result, String version) {
            ApiResponse response = new ApiResponse();
            response.setSuccess("200".equals(String.valueOf(v4Result.get("code"))));
            response.setMessage(String.valueOf(v4Result.get("msg")));
            response.setData(v4Result.get("v4_data"));
            response.setVersion(version);
            return response;
        }
        
        private boolean authenticate(String token) {
            // 统一的鉴权逻辑
            return token != null && token.startsWith("auth_");
        }
        
        private String extractV4AuthKey(String token) {
            // 从统一token中提取4.0系统需要的authKey
            return token.replace("auth_", "v4_key_");
        }
    }
    
    // 5.0系统适配器
    public class System5Adapter implements UnifiedApi {
        private System5Api system5Api;
        
        public System5Adapter(System5Api system5Api) {
            this.system5Api = system5Api;
        }
        
        @Override
        public ApiResponse execute(ApiRequest request) {
            // 统一的鉴权验证(和4.0适配器一样)
            if (!authenticate(request.getToken())) {
                return ApiResponse.fail("鉴权失败");
            }
            
            // 将统一参数转换成5.0系统需要的格式
            System5Request v5Request = new System5Request();
            v5Request.setAccessToken(extractV5Token(request.getToken()));
            v5Request.setOperation(request.getMethod());
            v5Request.setParameters(request.getParams());
            
            // 调用5.0系统
            System5Response v5Response = system5Api.invokeV5(v5Request);
            
            // 将5.0系统的响应转换成统一格式
            return convertToUnifiedResponse(v5Response, "5.0");
        }
        
        private ApiResponse convertToUnifiedResponse(System5Response v5Response, String version) {
            ApiResponse response = new ApiResponse();
            response.setSuccess(v5Response.getStatus() == 0);
            response.setMessage(v5Response.getMessage());
            response.setData(v5Response.getPayload());
            response.setVersion(version);
            return response;
        }
        
        private boolean authenticate(String token) {
            // 统一的鉴权逻辑
            return token != null && token.startsWith("auth_");
        }
        
        private String extractV5Token(String token) {
            // 从统一token中提取5.0系统需要的accessToken
            return token.replace("auth_", "v5_access_");
        }
    }
    
  4. 集成平台的使用

    // 集成平台服务
    public class IntegrationPlatform {
        private Map<String, UnifiedApi> adapters = new HashMap<>();
        
        public IntegrationPlatform() {
            // 初始化所有适配器
            adapters.put("4.0", new System4Adapter(new System4Api()));
            adapters.put("5.0", new System5Adapter(new System5Api()));
        }
        
        public ApiResponse callSystem(ApiRequest request) {
            // 根据版本选择对应的适配器
            UnifiedApi adapter = adapters.get(request.getVersion());
            if (adapter == null) {
                return ApiResponse.fail("不支持的版本: " + request.getVersion());
            }
            
            // 统一调用 - 平台代码完全不用关心底层是4.0还是5.0
            return adapter.execute(request);
        }
    }
    
  5. Client

    public class Client {
        public static void main(String[] args) {
            IntegrationPlatform platform = new IntegrationPlatform();
            
            // 准备统一参数
            Map<String, Object> params = new HashMap<>();
            params.put("userId", 123);
            params.put("userName", "张三");
            
            ApiRequest request = new ApiRequest();
            request.setToken("auth_123456789");
            request.setMethod("getUser");
            request.setParams(params);
            
            // 调用4.0系统
            request.setVersion("4.0");
            ApiResponse response4 = platform.callSystem(request);
            System.out.println("4.0响应: " + response4.getMessage());
            
            // 调用5.0系统(同一套参数和鉴权)
            request.setVersion("5.0");
            ApiResponse response5 = platform.callSystem(request);
            System.out.println("5.0响应: " + response5.getMessage());
        }
    }
    

3.4.2 案例2

场景分析

  • 底层有两套支付系统:支付宝,微信
  • 目标:想要让两套支付系统请求参数和返回参数保持统一

代码实现:

  1. 定义统一的目标接口

    定义一个想要的统一入参和返回参数接口

    public interface PayMent {
    
        BigDecimal pay(BigDecimal price);
    }
    
  2. 支付宝支付系统

    这套是支付宝系统接口需要的请求方法

    public class AaliPay {
    
        public Double aliPay(double price){
            System.out.println("支付宝支付:"+price);
            return price;
        }
    
    }
    
  3. 微信支付系统

    这套是微信支付系统的支付方法

    public class WeChatPay {
    
        public String pay(String price){
            System.out.println("微信支付"+price);
            return "100";
        }
    }
    
  4. 支付宝适配器

    public class AaliPayAdapter implements PayMent{
    
        private AaliPay aliPay;
    
        public AaliPayAdapter(AaliPay aliPay){
            this.aliPay=aliPay;
        }
        @Override
        public BigDecimal pay(BigDecimal price) {
            Double res = aliPay.aliPay(price.doubleValue());
            System.out.println("支付宝支付转换器");
            return new BigDecimal(res);
        }
    
    }
    
  5. 微信适配器

    public class WeChatPayAdapter implements PayMent{
    
        private WeChatPay weChatPay;
    
        public WeChatPayAdapter(WeChatPay weChatPay) {
            this.weChatPay = weChatPay;
        }
    
        @Override
        public BigDecimal pay(BigDecimal price) {
            String payRes = weChatPay.pay(price.toPlainString());
            System.out.println("微信支付系统转换器");
            return new BigDecimal(payRes);
        }
    }
    
  6. 支付工厂方法

    public class PayMentFactory {
    
        public static PayMent getPayment(PaymentEnums paymentEnums){
            switch (paymentEnums){
                case Ali:
                    AaliPay aaliPay = new AaliPay();
                    return new AaliPayAdapter(aaliPay);
                case WeChat:
                    WeChatPay weChatPay = new WeChatPay();
                    return new WeChatPayAdapter(weChatPay);
                default:
                    throw  new RuntimeException("不支持类型");
            }
        }
    }
    
  7. 支付系统枚举

    @Getter
    @AllArgsConstructor
    public enum PaymentEnums {
        WeChat(1,"微信"),
        Ali(2,"支付宝");
    
        private final Integer code;
        private final String desc;
    }
    
  8. 测试类

    public class AdapterClient {
        public static void main(String[] args) {
            PayMent aliPayMent = PayMentFactory.getPayment(PaymentEnums.Ali);
            PayMent webChatPayMent = PayMentFactory.getPayment(PaymentEnums.WeChat);
    
    
            BigDecimal aliPay = aliPayMent.pay(new BigDecimal("100"));
            BigDecimal webChatPay = webChatPayMent.pay(new BigDecimal("100"));
    
        }
    }
    

本案例使用了工厂方法模式 + 适配器模式;主要清楚一点,对于每种设计模式不要单一孤立的看,应该是:关于创建对象的想想建造者模式,关于结构的想想结构性模式,关于类的行为的去用行为模式;对于类的每个行动将其划分出来,然后使用对应的设计模式,就是组合使用设计模式了。

他们的职责划分是有意义的:

  • 创建型模式(解决对象创建问题)
  • 结构型模式(解决类和对象组合问题)
  • 行为型模式(解决对象间通信问题)

四,装饰者模式

核心思想:在不修改原类代码的前提下,动态地为对象添加新的功能

4.1 装饰器模式的结构

装饰器模式主要由以下 四个角色 组成:

Component(抽象组件)
│
├── ConcreteComponent(具体组件)
│
└── Decorator(抽象装饰类)
     │
     ├── ConcreteDecoratorA(具体装饰类A)
     └── ConcreteDecoratorB(具体装饰类B)

4.2 代码实现(接口版)

UML类图

       ┌───────────────────────┐
       │      Coffee (接口)     │
       │ + getDescription()    │
       │ + getCost()           │
       └───────────┬───────────┘
                   │
     ┌─────────────┴──────────────────┐
     │                                │
┌──────────────┐            ┌────────────────────────┐
│ SimpleCoffee │            │  CoffeeDecorator(抽象类)│
│ implements   │            │ implements Coffee       │
└──────────────┘            │ - coffee: Coffee        │
                            │ + getDescription()      │
                            │ + getCost()             │
                            └───────────┬─────────────┘
                                        │
             ┌──────────────────────────┴──────────────────────────┐
             │                                                     │
     ┌──────────────────┐                                 ┌──────────────────┐
     │ MilkDecorator     │                                 │ SugarDecorator   │
     │ extends Decorator │                                 │ extends Decorator│
     └──────────────────┘                                 └──────────────────┘

抽象组件

  • 定义对象的抽象接口(例如所有功能的公共方法)。
  • 是被装饰的对象和装饰器的共同父类或接口
public interface Coffee {
    String getDescription();
    double getCost();
}

具体组件

  • 实现了抽象组件的接口。
  • 最核心的功能类,即被“装饰”的原始对象。
public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple coffee";
    }

    @Override
    public double getCost() {
        return 5.0;
    }
}

抽象装饰类

  • 实现 Component 接口,但内部持有一个 Component 对象的引用
  • 将请求委托给这个被装饰的对象。
  • 为子类(具体装饰器)提供扩展点。
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee; // 被装饰对象

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

具体装饰类

  • 在继承 Decorator 的基础上,添加新的功能
  • 可以在方法前后扩展逻辑。
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", milk";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 1.5;
    }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", sugar";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 0.5;
    }
}

4.3 代码实现(抽象类版)

UML类图

        ┌──────────────────────┐
        │   Coffee (抽象类)     │
        │ + getDescription()   │
        │ + getCost() [abstract]│
        └───────────┬──────────┘
                    │
       ┌────────────┴────────────┐
       │                         │
┌──────────────┐       ┌────────────────────────┐
│ SimpleCoffee │       │ CoffeeDecorator(抽象类)│
│ extends Coffee│       │ extends Coffee          │
└──────────────┘       │ - coffee: Coffee        │
                       │ + getDescription()      │
                       │ + getCost()             │
                       └───────────┬─────────────┘
                                   │
                      ┌────────────┴─────────────┐
                      │ MilkDecorator extends     │
                      │ CoffeeDecorator           │
                      └───────────────────────────┘

抽象组件

public abstract class Coffee {
    public String getDescription() {
        return "Unknown coffee";
    }

    public abstract double getCost();
}

具体组件

public class SimpleCoffee extends Coffee {
    @Override
    public String getDescription() {
        return "Simple coffee";
    }

    @Override
    public double getCost() {
        return 5.0;
    }
}

抽象类装饰类

public abstract class CoffeeDecorator extends Coffee {
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public double getCost() {
        return coffee.getCost();
    }
}

具体装饰类

public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", milk";
    }

    @Override
    public double getCost() {
        return super.getCost() + 1.5;
    }
}

4.4 两种实现方式的区别

对比项接口版装饰器抽象类版装饰器
继承结构通过接口实现(轻)通过继承(重)
代码复用无共享逻辑可共享默认实现
灵活性高(可多实现)较低(单继承)
状态管理无状态可包含字段、默认实现
使用代表业务类(如 Coffee)框架底层(如 InputStream)

如何选择:

如果你……推荐用
只是想扩展功能、不需要状态接口版
想提供默认实现、需要共享逻辑抽象类版
想实现最大灵活性(比如叠加装饰)接口版
要写框架或工具类(像 IO 流、Servlet)抽象类版

4.5 装饰器模式的作用

  • 动态扩展对象的功能,而不是通过继承。
  • 让功能的添加变得更加灵活。
  • 可以“叠加”多个装饰器,从而形成一个功能链。

例如:

public class Main {
    public static void main(String[] args) {
        Coffee coffee = new SimpleCoffee();                      // 基础咖啡
        coffee = new MilkDecorator(coffee);                      // 加牛奶
        coffee = new SugarDecorator(coffee);                     // 加糖
        System.out.println(coffee.getDescription());             // 输出:Simple coffee, milk, sugar
        System.out.println(coffee.getCost());                    // 输出:7.0
    }
}

4.6 什么时候使用装饰器模式?

使用场景示例
想在不修改原有类的情况下为对象添加功能不想改第三方库或已有业务代码
需要动态组合多种功能比如:日志、加密、缓存可以自由叠加
不想使用继承造成类爆炸每种功能都用继承会导致类数量急剧膨胀

4.7 装饰器模式解决的问题

问题装饰器解决方式
子类继承导致类数量暴增改用对象组合的方式
想在运行时改变行为可动态包装不同装饰器
修改核心类困难(封闭原则)装饰器通过包装扩展行为

4.8 在 Java 标准库中的体现

其实 Java 自带了很多装饰器模式的例子,比如:

描述
java.io.BufferedInputStream 装饰 InputStream为输入流添加缓冲功能
java.io.DataInputStream 装饰 InputStream添加按类型读取功能
java.util.Collections.synchronizedList()返回线程安全的装饰版 List
javax.servlet.http.HttpServletRequestWrapper动态增强请求对象

4.9 抽象装饰类的作用

CoffeeDecorator 的作用有三个层面:

作用类型解释
类型统一它实现了 Coffee 接口,保证所有子类(装饰器)都能互相叠加(因为类型兼容)。
复用委托逻辑它封装了公共逻辑,比如 getDescription() 调用 coffee.getDescription()。每个具体装饰器不用重复写委托。
提供扩展模板它本身是抽象的,留出空位给具体装饰器做增强(开闭原则)。

简单一句话:

CoffeeDecorator 是装饰器链条的“粘合层”和“模板类”
它让每个装饰器既是“被装饰者”又是“装饰者”。

举个对比例子

  • 没有抽象装饰类(无法嵌套)

    public class MilkDecorator implements Coffee {
        private SimpleCoffee coffee;  // 写死类型
        ...
    }
    
    MilkDecorator milk = new MilkDecorator(new SimpleCoffee()); // OK
    SugarDecorator sugar = new SugarDecorator(milk); // 类型错误
    
  • 有抽象装饰类(可无限嵌套)

    public abstract class CoffeeDecorator implements Coffee {
        protected Coffee coffee;
        public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
    }
    
    public class MilkDecorator extends CoffeeDecorator { ... }
    public class SugarDecorator extends CoffeeDecorator { ... }
    
    Coffee c = new SugarDecorator(new MilkDecorator(new SimpleCoffee())); 
    

为什么 CoffeeDecorator 必须是“抽象”的?

因为:

  • 它只是提供公共的委托逻辑
  • 本身没有实际功能;
  • 不打算被直接实例化;
  • 所以用 abstract 明确表达“只能被继承,不能直接用”。

否则你可以写出奇怪的东西:

Coffee c = new CoffeeDecorator(new SimpleCoffee()); // 没有额外功能的装饰器

这在语义上是毫无意义的。

一句话总结:

CoffeeDecorator 是装饰器模式中维持“类型自洽”的关键,它让每个装饰器既实现了统一接口(能被别人装饰),又持有接口实例(能装饰别人)。
没它,就没有“无限叠加”的魔力。

五,桥接模式

5.1 桥接模式是什么

定义: 桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分实现部分分离,使它们可以独立变化

通俗理解: 它就是给你搭了一座“桥”,让两个本来紧耦合的维度解耦,从此互不干扰,各自独立扩展。

5.2 为什么需要桥接模式

想象一个需求:

你要开发一个“消息发送系统”,有不同的消息类型(普通消息、紧急消息),
同时支持多种发送方式(短信、邮件、微信...)。

如果你直接用继承来实现,可能会变成这样:

普通短信消息类
普通邮件消息类
紧急短信消息类
紧急邮件消息类
...

如果再加一个“特急消息”类型?类的数量就会 指数级增长

5.3 桥接模式的核心思想

桥接模式把系统拆成两个 **“维度”**:

维度含义举例
抽象层(Abstraction)表示消息的类型普通消息、紧急消息
实现层(Implementor)表示发送的方式短信、邮件、微信

然后通过“桥接”把两者组合起来:

不再用继承,而是通过 ** 组合(组合一个实现类的引用)** 的方式。

5.4 类图结构

        Abstraction(抽象类)
              ↑
          RefinedAbstraction(扩展抽象类)

              ↓(桥)
        Implementor(实现接口)
              ↑
        ConcreteImplementor(具体实现类)

5.5 代码实现

定义实现接口(消息发送方式)

/**
 * 消息发送
 */
public interface MessageSender {
    // 统一消息发送的行为接口
    public void sendMsg(String msg);
}

创建几个具体实现

public class EmailMessageSender implements MessageSender{
    @Override
    public void sendMsg(String msg) {
        System.out.println("邮箱发送");
    }
}

public class SmsMessageSender implements MessageSender{
    @Override
    public void sendMsg(String msg) {
        System.out.println("短信消息发送");
    }
}

定义抽象层(消息类型)

/**
 * 这个抽象层就是核心,如果需要给类扩展维度,就定义一个这个
 */
public abstract class MessageTypeBridge{
    //这个就相当于桥,这里不能定义成private
    protected MessageSender sender;

    public MessageTypeBridge(MessageSender sender) {
        this.sender = sender;
    }
    public abstract void sendMessageByType(String message);
}

扩展抽象类(不同类型的消息)

public class NormalMessageSender extends MessageTypeBridge{

    public NormalMessageSender(MessageSender sender) {
        super(sender);
    }

    @Override
    public void sendMessageByType(String message) {
        System.out.print("[普通消息]:");
        sender.sendMsg(message);
    }
}

public class UrgenrMessageSender extends MessageTypeBridge{

    public UrgenrMessageSender(MessageSender sender) {
        super(sender);
    }

    @Override
    public void sendMessageByType(String message) {
        System.out.print("[紧急消息]:");
        sender.sendMsg(message);
    }

}

客户端调用

public class Client {
    public static void main(String[] args) {
        MessageSender emailSender = new EmailMessageSender();
        MessageSender smsSender = new SmsMessageSender();

        MessageTypeBridge emailNormalMessageSender = new NormalMessageSender(emailSender);
        MessageTypeBridge smsNormalMessageSender = new NormalMessageSender(smsSender);
        emailNormalMessageSender.sendMessageByType("测试消息");
        smsNormalMessageSender.sendMessageByType("测试消息");


        System.out.println("------------------------------");

        MessageTypeBridge emailUrgentMessageSender = new UrgenrMessageSender(emailSender);
        MessageTypeBridge smsUrgentMessageSender = new UrgenrMessageSender(smsSender);
        emailUrgentMessageSender.sendMessageByType("测试消息");
        smsUrgentMessageSender.sendMessageByType("测试消息");
    }
}

输出结果:

[普通消息]:邮箱发送
[普通消息]:短信消息发送
------------------------------
[紧急消息]:邮箱发送
[紧急消息]:短信消息发送

5.6 桥接模式的优点

降低耦合度:抽象与实现分离,不互相依赖。
提高扩展性:新加消息类型 or 新的发送方式,不需要改动原代码。
符合开闭原则:对扩展开放,对修改关闭。

5.7 什么时候用桥接模式

用在需要同时扩展多个维度的场景:

场景示例
不同操作系统 + 不同文件格式Windows、Mac、Linux 各种播放器支持 mp3/mp4/avi
不同数据库 + 不同驱动方式MySQL、PostgreSQL 使用不同驱动
不同消息类型 + 不同发送渠道(就像上面我们的例子)

5.8 总结

桥接模式就是:用组合代替继承,把多维度变化的系统结构化、模块化,让它更灵活、更可扩展

理解:假设我们系统刚开始设计一个消息发送服务,我们这边统一定义了一个接口去管理消息发送的行为;此时消息发送只需要考虑底层实现;等到后续系统扩展了,业务上需要对消息进行类型等区分,这个并不是底层实现,这个相当于抽象层的东西,我们如果按照底层实现的规矩去新写一个消息类型接口,然后底层实现分别去实现消息类型就会导致类的数量 = 消息类型 * 底层实现;如果后续再加就是 其他抽象 * 消息类型 * 底层实现,这样会导致每添加一种抽象,代码的数量就会指数级增长,这个是不现实的;而桥接模式就是为了解决这个问题产生的;我们将抽象和底层实现分开,通过将抽象层的东西归于抽象类中,然后引用实现层从而实现组合而非继承,从而解决这个类爆炸的问题.

六,外观模式

核心思想

  • 为子系统中的一组复杂接口提供一个统一的高层接口,使得子系统更容易使用。
  • 简单来说:“外部只和我这个‘门面’打交道,内部的复杂逻辑我自己搞定。”

6.1 为什么需要它

假设你现在有一个 开票系统,它内部有一堆子系统:

  • 税控系统接口
  • 发票模板服务
  • 校验签章模块
  • 开票记录日志模块
  • 通知发送模块(短信、邮件等)

业务调用时必须依次调用这些服务,还要处理异常、日志、事务、回滚等等。

// 没有外观模式的情况下
taxApi.validate();
templateService.load();
invoiceCore.create();
logService.record();
notifyService.send();

每次业务都要写一遍这些步骤,代码臃肿、难维护。

6.2 引入外观模式

我们设计一个外观类:

public class InvoiceFacade {

    private TaxApi taxApi;
    private TemplateService templateService;
    private InvoiceCore invoiceCore;
    private LogService logService;
    private NotifyService notifyService;

    public InvoiceFacade(TaxApi taxApi, TemplateService templateService,
                         InvoiceCore invoiceCore, LogService logService,
                         NotifyService notifyService) {
        this.taxApi = taxApi;
        this.templateService = templateService;
        this.invoiceCore = invoiceCore;
        this.logService = logService;
        this.notifyService = notifyService;
    }

    public void createInvoice(InvoiceRequest request) {
        taxApi.validate(request);
        templateService.loadTemplate(request.getTemplateId());
        invoiceCore.createInvoice(request);
        logService.record("开票成功: " + request.getInvoiceNo());
        notifyService.send(request.getUserId(), "发票已开具");
    }
}

外部使用时:

// 控制器层只需要这一行
invoiceFacade.createInvoice(request);

控制层(Controller)不再关心内部调用多少个模块。

6.3 外观模式的 核心价值

作用描述
简化调用外部系统只调用一个类
屏蔽复杂性内部逻辑随便改,不影响外部
更易维护修改子系统不需要改业务层
代码解耦业务 → 外观层 → 子系统,各层职责清晰

七,组合模式

7.1 核心思想

组合模式(Composite Pattern): 让“单个对象(叶子节点)”与“组合对象(容器节点)”在使用上具有一致性。

通俗讲就是:“我希望单个元素和一组元素都能被统一处理。”

7.2 为什么要有组合模式

想象一个文件系统:

  • 文件(File)
  • 也有 文件夹(Folder)
  • 文件夹里还能再包含文件或文件夹

你希望能写出这样统一的代码:

rootFolder.showInfo();

系统能自动递归打印出整个目录树,不需要区分“是文件”还是“文件夹”,这就是组合模式的威力。

7.3 UML 结构图

        ┌──────────────────────────┐
        │       Component          │ <─── 抽象组件(定义通用行为)
        │ + showInfo()             │
        └──────────┬───────────────┘
                   │
      ┌────────────┴───────────────┐
      │                            │
┌────────────┐           ┌──────────────────┐
│  Leaf      │           │  Composite       │
│ 文件节点   │           │ 文件夹节点       │
│ + showInfo()│           │ + add(Component)│
│              │           │ + showInfo()   │
└────────────┘           └──────────────────┘

7.4 Java 实现示例

抽象组件

// 抽象组件
interface FileComponent {
    void showInfo(String indent);
}

叶子节点(文件)

// 叶子节点(文件)
public class FileLeaf implements FileComponent {
    private String name;

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

    @Override
    public void showInfo(String indent) {
        System.out.println(indent + " 文件:" + name);
    }
}

容器节点(文件夹)

// 容器节点(文件夹)
class FolderComposite implements FileComponent {
    private String name;
    private java.util.List<FileComponent> children = new java.util.ArrayList<>();

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

    public void add(FileComponent component) {
        children.add(component);
    }

    @Override
    public void showInfo(String indent) {
        System.out.println(indent + " 文件夹:" + name);
        for (FileComponent child : children) {
            child.showInfo(indent + "   ");
        }
    }
}

测试

// 测试
public class CompositeDemo {
    public static void main(String[] args) {
        // 创建文件和文件夹结构
        FileLeaf file1 = new FileLeaf("设计模式笔记.txt");
        FileLeaf file2 = new FileLeaf("外观模式.png");
        FolderComposite folder1 = new FolderComposite("结构型模式");
        folder1.add(file1);
        folder1.add(file2);

        FolderComposite root = new FolderComposite("学习笔记");
        root.add(folder1);
        root.add(new FileLeaf("README.md"));

        // 一行调用,递归展示结构
        root.showInfo("");
    }
}

输出结果:

 文件夹:学习笔记
    文件夹:结构型模式
       文件:设计模式笔记.txt
       文件:外观模式.png
    文件:README.md

7.5 应用场景

“当你的对象形成一种树形层级结构(组织、菜单、文件、UI 组件、权限体系等),并且想让外部代码不关心它是单个元素还是一组元素时,就该用组合模式。”

  1. 文件系统

    • 结构:文件夹内可以包含文件或文件夹。

    • 操作:对文件和文件夹都能执行如“删除”“复制”“解压”等相同行为

    • 实际例子:Windows 的资源管理器、Linux 的目录结构。

      同样的 delete() 方法,既能删除单个文件,也能删除整个文件夹。

  2. 菜单系统

    • 结构:一个菜单(Menu)可以包含子菜单(SubMenu)或菜单项(MenuItem)

      菜单
      ├── 文件
      │   ├── 新建
      │   ├── 打开
      │   └── 退出
      ├── 编辑
      │   ├── 撤销
      │   └── 粘贴
      └── 帮助
          └── 关于
      
    • 操作:用户点击操作都是一样的行为(如显示、跳转)

      menu.render(); // 不管它是菜单项还是子菜单,都能统一显示
      
  3. 组织结构

 -   **结构**:部门里有子部门,也有员工。
 -    **行为**:计算总薪资、发送通知、绩效考核……

 -   无论是部门还是员工,都有 `getSalary()` 或 `notifyMessage()` 方法。

     ```java
     interface OrgUnit {
         void notifyMessage(String msg);
     }
     
     class Employee implements OrgUnit { ... }
     class Department implements OrgUnit {
         List<OrgUnit> members;
         ...
     }
     ```

4. 权限 / 菜单树

 在权限系统中,每个节点可以是:

 -   功能模块(父节点)
 -   具体操作按钮(子节点)

 对外暴露统一的接口,不用关心是“**功能**”还是“**具体按钮**”。

 ```java
 permission.check(user);
 ```

总结

关键特征是否适合组合模式
对象存在层级结构(树状)
需要对单个对象和组合对象统一处理
操作具有递归性(如执行、渲染、计算)
组合中元素类型相同或相似
对象间依赖较弱,不需要复杂交互

八,享元模式

8.1 享元模式核心思想

享元模式(Flyweight Pattern): 通过共享对象来减少系统中对象的数量,以节省内存和提高性能。

通俗理解:“如果很多对象长得一样,就不要重复创建,用同一个对象就够了。”

关键点:

  1. 共享相同状态(内部状态)
  2. 把变化的状态(外部状态)通过参数传入
  3. 通过工厂来管理共享对象

8.2 为什么要用享元模式

当系统中需要创建大量对象,且这些对象大部分内容相同时,如果每次都 new 一个对象,会占用大量内存

比如:

  • 文本编辑器中的字符对象
  • 游戏中的棋子、子弹
  • UI 中的重复图标

如果每个都创建一个对象 → 内存占用很大,如果共享相同对象 → 内存节省很多

8.3 享元模式结构

+-----------------+
|    Flyweight    |  <-- 抽象享元接口
| + operation(extrinsicState)
+-----------------+
       ^
       |
+-----------------+         +-----------------+
| ConcreteFlyweight |        | UnsharedFlyweight (可选)
+-----------------+         +-----------------+
| + intrinsicState|         |  + 不共享状态    |
+-----------------+         +-----------------+
       ^
       |
+-----------------+
| FlyweightFactory |
+-----------------+
| + getFlyweight()|
+-----------------+
  • Intrinsic State(内部状态):可共享的,不会随外部改变
  • Extrinsic State(外部状态):随使用场景变化,由外部传入

8.4 代码实现

享元接口

// 享元接口
public interface Icon {
    void draw(IconRenderContext context);
}

具体享元

// 具体享元类(内部状态:图标文件路径)
public class ConcreteIcon implements Icon {

    private final String iconPath;

    public ConcreteIcon(String iconPath) {
        this.iconPath = iconPath;
        // 模拟加载图标资源
        System.out.println("加载图标资源: " + iconPath);
    }

    @Override
    public void draw(IconRenderContext context) {
        // 使用外部状态(位置、透明度)
        System.out.printf("绘制图标 [%s] at (%d,%d), size=(%dx%d), alpha=%.1f%n",
                iconPath, context.x, context.y, context.width, context.height, context.alpha);
    }
}

外部状态

// 外部状态:图标显示参数(位置/大小/透明度)
public class IconRenderContext {
    int x, y;
    int width, height;
    float alpha;

    public IconRenderContext(int x, int y, int width, int height, float alpha) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.alpha = alpha;
    }
}

享元工厂

// 享元工厂(缓存共享图标)
public class IconFactory {
    private final Map<String, Icon> iconPool = new ConcurrentHashMap<>();

    private static final IconFactory INSTANCE = new IconFactory();

    private IconFactory() {}

    public static IconFactory getInstance() { return INSTANCE; }

    public Icon getIcon(String path) {
        return iconPool.computeIfAbsent(path, ConcreteIcon::new);
    }

    public int getPoolSize() {
        return iconPool.size();
    }

}

测试

// 使用示例
public class FlyweightIconDemo {
    public static void main(String[] args) {
        IconFactory factory = IconFactory.getInstance();

        // 多次获取相同图标(只加载一次)
        Icon userIcon1 = factory.getIcon("icons/user.png");
        Icon userIcon2 = factory.getIcon("icons/user.png");
        Icon folderIcon = factory.getIcon("icons/folder.png");

        // 绘制时传入不同外部状态
        userIcon1.draw(new IconRenderContext(10, 10, 32, 32, 1.0f));
        userIcon2.draw(new IconRenderContext(100, 50, 64, 64, 0.8f));
        folderIcon.draw(new IconRenderContext(200, 20, 48, 48, 1.0f));

        System.out.println("图标池中图标数量:" + factory.getPoolSize());
    }
}

输出示例:

加载图标资源: icons/user.png
加载图标资源: icons/folder.png
绘制图标 [icons/user.png] at (10,10), size=(32x32), alpha=1.0
绘制图标 [icons/user.png] at (100,50), size=(64x64), alpha=0.8
绘制图标 [icons/folder.png] at (200,20), size=(48x48), alpha=1.0
图标池中图标数量:2

8.5 应用场景总结

  • 业务层(几乎不用)

    在常规的企业业务系统里,比如:用户管理、订单、开票、库存、报表……;这些都是数据驱动的业务,不会频繁 new 上万个逻辑相同的对象。

    所以享元模式在这层没太大用处,因为:

    • 节省的内存有限
    • 复杂度反而上升
    • Spring 本身就有 Bean 复用机制(单例 Bean)

    简单来说:得不偿失

  • 框架层(非常常见)

    在框架里就不同了。框架通常要频繁创建和管理对象,性能与内存消耗敏感。

    典型例子 :

    框架享元思想体现说明
    Spring单例 Bean、@Scope("singleton")每个 Bean 只实例化一次,全局共享
    MyBatisMapper 接口代理缓存每个 Mapper 对应的代理类在内部缓存复用
    JDBC 连接池Connection 复用池化思想,本质就是享元:同类资源共享
    NettyByteBuf 池、ChannelHandler 共享高性能网络通信中频繁复用对象
    ThreadPoolExecutorWorker 线程复用多线程池中线程作为享元对象共享执行任务

    这些都是享元思想的变体:控制对象创建 + 管理共享资源。

  • 性能敏感系统

    如果是图形系统、游戏、仿真系统、监控系统等,就会频繁用到享元。

    比如:

    场景示例
    游戏棋盘格、子弹、怪物模型(重复太多)
    GIS 系统地图瓦片对象共享
    大屏监控数据点共享样式、节点缓存
    报表系统单元格模板共享、字体样式共享

    这些场景下,享元模式能显著减少内存占用。

总结一句话:

  • 业务层:几乎不用(除非极端性能要求)
  • 框架层:大量使用(对象池、单例、Bean 缓存等)
  • 底层系统:频繁使用(图形、游戏、网络通信)

8.6 总结

享元模式就是通过工厂控制对象的创建,使得相同的对象只被创建一次,并在不同场景中共享使用

小结:

  • 享元模式 ≈ 对象池 + 内部状态共享
  • 用法和组合模式、桥接模式不同,它的核心关注点是节省内存和对象复用
  • 在非 Spring 项目中 —— 享元模式需要手动写工厂 + 缓存;
  • 在 Spring 项目中 —— Spring IoC 容器本身就是一个天然的享元工厂