设计模式 三、结构型设计模式
一、代理模式
        代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供了一个代理,以控制对这个对象的访问。 代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。简单来说,代理模式 就是通过代理对象来控制对实际对象的访问,代理对象在客户端和目标对象之间起到了中介的作用。
         在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理类在运行时动态生成。
1、静态代理
        静态代理是在代码编译阶段就已经生成了代理类,代理类需要实现目标对象相同的接口。
         优点:可以在不修改目标对象的前提下对目标对象进行增强。
         缺点:需要为每个目标创建一个代理类,导致系统中类的数量增加,维护成本较高。
2、静态代理的使用场景
2.1 缓存代理
缓存代理是一种特殊类型的的代理模式,它可以为耗时的操作或者重复的请求提供缓存功能,从而提高程序的执行效率。缓存代理通常会在内部维护一个缓存的数据结构。如HashMap 或者 LinkHashMap,用来 存储已经处理过的请求及其结果。
以下是一个示例:
假设有一个数据查询接口,它从数据库或者其他数据源中检索数据,在没有缓存代理的情况下,每次查询都需要访问数据库,这可能会导致较高的资源消耗和延迟,通过引入缓存代理,我们可以将查询结果存储在内存中,从而避免重复查询数据库。
首先定义一个数据查询接口:
public interface DataQuery {
    String query(String queryKey);
} 
然后,实现一个真实的数据查询类:
public class DatabaseDataQuery implements DataQuery {
    @Override
    public String query(String queryKey) {
    // 查询数据库并返回结果
        return "Result from database: " + queryKey;
    }
} 
然后创建一个缓存代理类,它实现DataQuery接口,并在内部使用HashMap作为缓存:
public class CachingDataQueryProxy implements DataQuery {
    private final DataQuery realDataQuery;
    private final Map<String, String> cache;
    public CachingDataQueryProxy(DataQuery realDataQuery) {
        this.realDataQuery = new DatabaseDataQuery();
        cache = new HashMap<>();
    }
    @Override
    public String query(String queryKey) {
        String result = cache.get(queryKey);
        if (result == null) {
            result = realDataQuery.query(queryKey);
            cache.put(queryKey, result);
            System.out.println("Result retrieved from database and added to cache.");
        } else {
            System.out.println("Result retrieved from cache.");
        }
        return result;
    }
} 
最后,我们在客户端就可以使用缓存代理:
public class Client {
    public static void main(String[] args) {
        DataQuery realDataQuery = new DatabaseDataQuery();
        DataQuery cachingDataQueryProxy = new
                CachingDataQueryProxy(realDataQuery);
        String queryKey = "example_key";
        // 第一次查询,从数据库中获取数据并将其缓存
        System.out.println(cachingDataQueryProxy.query(queryKey));
        // 第二次查询相同的数据,从缓存中获取
        System.out.println(cachingDataQueryProxy.query(queryKey));
    }
} 
其实也就是说我们单独实现了一个方法,我们在这个方法中取调用原本的查询操作,也就是在这个新的方法中完成了过滤的操作。
2.2 安全代理
        (Security Proxy)是一种代理模式用的应用,它用于控制对真实主题对象的访问。通过安全代理,可以实现访问控制、权限控制等安全相关功能。
        
         以下是一个简单的安全代理示例:
        
         假设我们有一个敏感数据查询接口,只有具有特定权限的用户才能访问:
         首先,我们定义一个数据查询接口:
public interface SensitiveDataQuery {
    String queryData(String userId);
} 
接着实现一个真的敏感数据查询类:
public class SensitiveDataQueryImpl implements SensitiveDataQuery {
    @Override
    public String queryData(String userId) {
        // 查询敏感数据并返回结果
        return "Sensitive data for user: " + userId;
    }
} 
然后,我们创建一个安全代理类,它实现了 SensitiveDataQuery接口,并在内部进行权限验证:
public class SecurityProxy implements SensitiveDataQuery {
    private final SensitiveDataQuery sensitiveDataQuery;
    private final UserAuthenticator userAuthenticator;
    public SecurityProxy(SensitiveDataQuery sensitiveDataQuery,
                         UserAuthenticator userAuthenticator) {
        this.sensitiveDataQuery = sensitiveDataQuery;
        this.userAuthenticator = userAuthenticator;
    }
    @Override
    public String queryData(String userId) {
        if (userAuthenticator.hasPermission(userId)) {
            return sensitiveDataQuery.queryData(userId);
        } else {
            return "Access Denied: Insufficient permission for user" + userId;
        }
    }
} 
我们使用一个UserAuthenticator类来模拟用户权限验证:
public class UserAuthenticator {
    private final List<String> authorizedUserIds;
    public UserAuthenticator() {
    // 模拟从数据库或配置文件中获取已授权的用户列表
        authorizedUserIds = Arrays.asList("user1", "user2", "user3");
    }
    public boolean hasPermission(String userId) {
        return authorizedUserIds.contains(userId);
    }
} 
在客户端中调用:
public class Client {
    public static void main(String[] args) {
        SensitiveDataQuery sensitiveDataQuery = new SensitiveDataQueryImpl();
        UserAuthenticator userAuthenticator = new UserAuthenticator();
        SensitiveDataQuery securityProxy = new SecurityProxy(sensitiveDataQuery,
                userAuthenticator);
        String userId1 = "user1";
        String userId2 = "user4";
        // 用户1具有访问权限
        System.out.println(securityProxy.queryData(userId1));
        // 用户4没有访问权限
        System.out.println(securityProxy.queryData(userId2));
    }
} 
2.3 虚拟代理
(Virtual Proxy)是一种代理模式,用于在需要时延迟创建耗时或资源密集型对象。虚拟代理在初始访问时才创建实际对象,之后将直接使用该对象。这可以避免在实际对象尚未使用的情况下就创建它,从而节省资源。
例如:我们有一个大型图片类,他从网络加载图像。由于图像可能非常大。我们希望在需要显示时才加载他,为了实现这一点,我们可以创建一个虚拟代理来代表大型图片类,首先定义一个图片接口:
public interface Image {
    void display();
} 
然后实现了一个大型图片类,他从网络加载图像并实现display() 方法:
public class LargeImage implements Image {
    private final String imageUrl;
    public LargeImage(String imageUrl) {
        this.imageUrl = imageUrl;
        loadImageFromNetwork();
    }
    private void loadImageFromNetwork() {
        System.out.println("Loading image from network: " + imageUrl);
    // 真实的图像加载逻辑...
    }
    @Override
    public void display() {
        System.out.println("Displaying image: " + imageUrl);
    }
} 
然后创建一个虚拟代理类,它实现了Image接口,并在内部使用LargeImage:
public class VirtualImageProxy implements Image {
    private final String imageUrl;
    private LargeImage largeImage;
    public VirtualImageProxy(String imageUrl) {
        this.imageUrl = imageUrl;
    }
    @Override
    public void display() {
        if (largeImage == null) {
            largeImage = new LargeImage(imageUrl);
        }
        largeImage.display();
    }
} 
最后我们在客户端中使用虚拟代理:
public class Client {
    public static void main(String[] args) {
        Image virtualImageProxy = new
                VirtualImageProxy("https://example.com/large-image.jpg");
        System.out.println("Image will not be loaded until it is displayed.");
        // 调用 display() 方法时,才会创建并加载大型图片
        virtualImageProxy.display();
    }
} 
这个例子就是通过虚拟代理实现懒加载,以减少资源消耗和提高程序性能,当实际对象的创建和初始化非常耗时或占用大量资源时,虚拟代理是一个很好的选择。
2.4 远程代理
        (Remote Proxy)是一种代理模式,用于访问位于不用的地址空间的对象。远程代理可以为本地对象提供与远程对象相同的接口,使得客户端可以透明的访问远程对象。通常,远程代理需要处理网络通信、序列化和反序列化等细节。以后做rpc时也会使用。
         
         简单的示例:首先定义一个服务接口:
public interface RemoteService {
    String fetchData(String dataId);
} 
然后实现一个远程服务类,在服务端运行并实现fetchData()方法:
public class RemoteServiceImpl implements RemoteService {
    @Override
    public String fetchData(String dataId) {
        // 实际操作,例如从数据库获取数据
        return "Data from remote service: " + dataId;
    }
} 
· 接下来,我们创建了一个远程代理类,它实现了RemoteService接口,并在内部处理网络通信等细节:
public class RemoteServiceProxy implements RemoteService {
    private final String remoteServiceUrl;
    private RemoteService remoteService;
    public RemoteServiceProxy(String remoteServiceUrl) {
        this.remoteServiceUrl = remoteServiceUrl;
        this.remoteService = new RemoteService();
    }
    @Override
    public String fetchData(String dataId) {
        // 网络通信、序列化和反序列化等逻辑
        System.out.println("Connecting to remote service at: " + remoteServiceUrl);
        // 假设我们已经获取到远程服务的数据
        String result = remoteService.fetchData(dataId);
        System.out.println("Received data from remote service.");
        return result;
    }
} 
2.5 静态代理步骤总结
        通过前面几个案例,大致了解了静态代理的使用方式,其大致流程如下:
         1.创建一个接口,定义代理类和被代理类共同实现的方法。
         2.创建被代理类,实现这个接口,并且在其中定义实现方法。
         3.创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量。
         4.在代理类中实现接口中的方法,方法中调用被代理类中的对应方法。
         5.通过创建代理对象,并调用其方法,方法增强。
这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。
        在静态代理中,也可以使用继承来实现代理,具体步骤如下:
         1.创建被代理类,定于需要被代理的方法。
         2.创建代理类,继承被代理类,重写被代理类中的方法,对方法进行增强。
         3.再重写的方法中添加代理逻辑,例如在调用被代理类中的方法前后添加日志记录、安全检查等功能。
         4.在使用代理类时,创建代理对象,调用重写方法。
         这样,被代理类的方法就会被代理类所覆盖,实现了对被代理类的增强或修改。使用继承来实现代理类的好处就是简单易懂,不需要创建接口,同时继承可以继承被代理类的属性和方法,可以更方便的访问类中的成员。但是这个方法也有一些缺点,类如代理类与被代理类的耦合度较高,不够灵活。
3、动态代理
        java中动态代理的实现方式主要有两种:基于JDK的动态代理和基于CGLB的动态代理。
        
         动态代理是指在程序运行时动态生成代理类,无需手动编写代理类,大大降低了代码的复杂度,动态代理一般使用Java提供的反射机制实现,可以对任意实现了接口的类进行代理。动态代理的优点是灵活性高,可以根据需要动态生成代理类,缺点是性能相对较低,由于使用反射机制,在运行时会产生额外的开销。
         静态代理 和 动态代理都是代理模式的实现方式,其主要区别在于代理类的生成时机和方式。静态代理需要手动编写代理类,适用于代理类数量较少,不需要频繁修改的场景。而动态代理不需要手动编写代理类,可以动态的生成代理类,适用于代理类数量较多、需要频繁修改的场景。
4.1 基于JDK的动态代理实现步骤
        基于JDK的动态代理需要使用 java.lang.reflect.Proxy 类 和 Java.lang.reflect.InvocationHandler 接口。我们依旧使用上述的缓存代理的案例来实现,具体步骤如下:
        
         1)定义一个接口,声明需要代理的方法:
public interface DataQuery {
    String query(String queryKey);
    String queryAll(String queryKey);
} 
2)创建一个被代理类,实现这个接口,并在其中定义实现方法:
public class DatabaseDataQuery implements DataQuery {
    @Override
    public String query(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "result";
    }
    @Override
    public String queryAll(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "all result";
    }
} 
3)创建一个代理类,实现了InvocationHandler接口,并在其中定义一个被代理类的对象作为属性。
public class CacheInvocationHandler implements InvocationHandler {
    private HashMap<String,String> cache = new LinkedHashMap<>(256);
    private DataQuery databaseDataQuery;
    public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {
        this.databaseDataQuery = databaseDataQuery;
    }
    public CacheInvocationHandler() {
        this.databaseDataQuery = new DatabaseDataQuery();
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1、判断是哪一个方法
        String result = null;
        if("query".equals(method.getName())){
            // 2、查询缓存,命中直接返回
            result = cache.get(args[0].toString());
            if(result != null){
                System.out.println("数据从缓存重获取。");
                return result;
            }
            // 3、未命中,查数据库(需要代理实例)
            result = (String) method.invoke(databaseDataQuery, args);
            // 4、如果查询到了,进行呢缓存
            cache.put(args[0].toString(),result);
            return result;
        }
        // 当其他的方法被调用,不希望被干预,直接调用原生的方法
        return method.invoke(databaseDataQuery,args);
    }
} 
在代理类中,我们实现了InvocationHandler接口,并在其中定义了一个被代理类的对象作为属性。在invoke方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。
4)使用代理类,创建被代理类的对象和代理类的对象,并使用Proxy.newProxyInstance方法生成代理对象。
public class Main {
    public static void main(String[] args) {
        // jdk提供的代理实现,主要是使用Proxy类来完成
        // 1、classLoader:被代理类的类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 2、代理类需要实现的接口数组
        Class[] interfaces = new Class[]{DataQuery.class};
        // 3、InvocationHandler
        InvocationHandler invocationHandler = new CacheInvocationHandler();
        DataQuery dataQuery = (DataQuery)Proxy.newProxyInstance(
                classLoader, interfaces, invocationHandler
        );
        // 事实上调用query方法的使用,他是调用了invoke
        String result = dataQuery.query("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.query("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.query("key2");
        System.out.println(result);
        System.out.println("++++++++++++++++++++++++++++++++++++");
        // 事实上调用queryAll方法的使用,他是调用了invoke
        result = dataQuery.queryAll("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.queryAll("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.queryAll("key2");
        System.out.println(result);
        System.out.println("--------------------");
    }
} 
         再看一下生成日志的例子:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface UserService {
    void saveUser(String username);
}
class UserServiceImpl implements UserService {
    public void saveUser(String username) {
        System.out.println("Saving user: " + username);
    }
}
class LogProxy implements InvocationHandler {
    private Object target;
    public LogProxy(Object target) {
        this.target = target;
    }
    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;
    }
}
public class DynamicProxyExample {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new LogProxy(userService)
        );
        proxy.saveUser("Alice");
    }
} 
其实主要的步骤就是要编写我们的动态代理类,并在invoke 方法中书写自己需要添加的逻辑,Proxy.newProxyInstance 方法生成代理对象,这一步很多人看不懂,其实不需要担心,因为这个其实就是一个固定的api知道怎么调用即可!
4.2 基于CGLIB的动态代理实现步骤
基于CGLB的动态代理需要使用 net.sf.cglib.proxy.Enhancer类 和 net.sf.cglib.proxy.MethodInterceptor 接口 ,具体步骤如下:
1)创建一个被代理类,定义需要被代理的方法:
public class DatabaseDataQuery {
    public String query(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "result";
    }
    public String queryAll(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "all result";
    }
} 
2)创建一个方法拦截器类,实现MethodInterceptor接口,并在其中定义一个被代理类的对象作为属性。
public class CacheMethodInterceptor implements MethodInterceptor {
    private HashMap<String,String> cache = new LinkedHashMap<>(256);
    private DatabaseDataQuery databaseDataQuery;
    public CacheMethodInterceptor() {
        this.databaseDataQuery = new DatabaseDataQuery();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        // 1、判断是哪一个方法
        String result = null;
        if("query".equals(method.getName())){
            // 2、查询缓存,命中直接返回
            result = cache.get(args[0].toString());
            if(result != null){
                System.out.println("数据从缓存重获取。");
                return result;
            }
            // 3、未命中,查数据库(需要代理实例)
            result = (String) method.invoke(databaseDataQuery, args);
            // 4、如果查询到了,进行呢缓存
            cache.put(args[0].toString(),result);
            return result;
        }
        return method.invoke(databaseDataQuery,args);
    }
} 
在这个代理类中,我们实现了MethodInterceptor 接口,并在其中定义了一个被代理类的对象作为属性,在intercept方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。
3)在使用代理类时,创建被代理类的对象和代理类的对象,并使用Enhancer.create方法生成代理对象。
public class Main {
    public static void main(String[] args) {
        // cglib通过Enhancer
        Enhancer enhancer = new Enhancer();
        // 设置他的父类
        enhancer.setSuperclass(DatabaseDataQuery.class);
        // 设置一个方法拦截器,用来拦截方法
        enhancer.setCallback(new CacheMethodInterceptor());
        // 创建代理类
        DatabaseDataQuery databaseDataQuery =
                (DatabaseDataQuery)enhancer.create();
        databaseDataQuery.query("key1");
        databaseDataQuery.query("key1");
        databaseDataQuery.query("key2");
    }
} 
 
实现打印日志的例子:
class UserServiceImpl {
    public void saveUser(String username) {
        System.out.println("Saving user: " + username);
    }
}
class LogInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}
public class CglibProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(new LogInterceptor());
        UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
        proxy.saveUser("Bob");
    }
} 
        在这个示例中,我们使用 Enhancer.create 方法生成代理对象,并将代理对象转成RealSubject类型,以便调用request方法,在代理对象调用request方法时,会调用DynamicProxy类中的intercept方法,实现对被代理对象的增强。
         在实际应用中,基于CGLIB的动态代理可以代理任意类,但是生成的代理类比较重量级。如果被代理类是一个接口,建议使用基于JDK的动态代理来实现,这也是spring的做法;如果被代理类没有实现接口或者需要代理的方法时final方法,建议使用基于CGLIB的动态代理来实现。
4.3 spring aop 与 动态代理之间的关系
spring aop 即面向切面编程,他对aop进行了封装,使用面向对象的思想来实现,所以aop的底层使用动态代理来实现的。
4、动态代理的应用场景
4.1 日志记录:使用动态代理可以在方法调用前后自动添加日志记录,从而跟踪方法的执行过程。这样可以方便的监控系统运行情况,诊断问题,而无需修改实际类的代码。
4.2 性能监控:动态代理可用于测量方法执行的时间,以评估性能。在方法调用前后记录时间戳,然后计算时间差,就可以得到方法执行所需要的时间。
4.3 事务管理:在数据库操作中,动态代理可用于自动管理事务。在方法调用前开始一个事务,在方法成功执行后提交事务,如果发生异常,则回滚事务,这样可以确保数据的一致性和完整性。
4.4 权限验证:使用动态代理可以在方法调用前进行权限验证,确保具有适当权限的用户才能访问受保护的资源。这可以提高系统的安全性。
4.5缓存:动态代理可用于实现方法结果的的缓存。在方法调用前检查缓存,如果缓存中有结果,则直接返回,否则执行方法并将结果存入缓存。这样可以提高程序的执行效率。
4.6 负载均衡与故障转移:在分布式系统中,动态代理可以用于实现负载均衡和故障转移。代理对象根据某种策(入轮询、随机等)选择一个可用的服务实例。并将请求转发给它。如果服务实例发生故障,代理对象可以自动选择另一个可用的实例。
4.7 API速率限制:使用动态代理,可以在方法调用前检查aoi请求速率是否超过预设的限制。如果超过限制,可以拒绝请求获奖请求延迟一段时间后再执行。
4.8 数据验证:在方法调用前,动态代理可以用于验证传入的参数是否符合预期的规则和约束。这有助于确保数据的有效性和一致性。
4.9 重试机制:当方法调用失败时,比如网络问题等等,动态代理可以实现自动重试的机制,代理对象可以在一定的时间间隔内尝试重新执行方法,直到成功达到最大重试次数。
4.10 懒加载与资源管理:动态代理可以用于实现资源的懒加载和管理。例如,代理对象可以在第一次访问资源时才创建和初始化它。此外,代理对象还可以在资源不再需要时自动释放它,以减少内存占用和提高性能。
4.11 跨语言和跨平台调用:动态代理可以实现跨语言和跨平台的对象调用。例如,一个java客户端可以使用动态代理调用一个基于python的服务,在这种情况下,代理对象会负责跨语言通信的细节,如序列化、反序列化和网络传输。
4.12 AOP(面向切面编程):动态代理是实现AOP的一种方式。AOP允许在程序运行时动态的插入和修改横切关注点(如日志记录、性能监控等),而无需修改实际代码。动态代理可以轻松地实现AOP,以提高代码的可维护性和可重用性。
具体例子省略,可自行查询相关例子。
二、装饰器模式
1、实现原理
装饰器设计模式是一种结构型设计模式,它允许动态的为对象添加新的行为,它通过创建一个包装器来实现。即将对象放入一个装饰器中,再将装饰器类放入另一个装饰器类中,以此类推,形成一个包装链。这样我们可以在不改变原有对象的情况下 ,动态的添加新的行为或修改原有行为。
实现步如下:
1)定义一个 接口或者抽象类,作为被装饰对象的基类
例如创建一个component的接口,包含一个名为operation 的抽象方法,用于定义被装饰对象的基本行为。
public interface Component {
    void operation();
} 
2)定义一个具体的被装饰对象,实现基类中的方法
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent is doing something...");
    }
} 
3)定义一个抽象装饰类,继承基类,并将被装饰对象作为属性
public abstract class Decorator implements Component {
    protected Component component;
    public Decorator(Component component) {
        this.component = component;
    }
    @Override
    public void operation() {
        component.operation();
    }
} 
4)定义具体的装饰器类,继承抽象装饰器类,并实现增强逻辑
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorA is adding new behavior...");
    }
} 
5)使用装饰器增强被装饰对象
public class Main {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component = new ConcreteDecoratorA(component);
        component.operation();
    }
}
 
上面的过程跟静态代理有一些相似之处,但他们之间还是有区别的:
        代理模式的目的是为了控制对象的访问,他在对象的外部提供了一个代理对象来控制对原始对象的访问。代理对象和原始对象通常实现同一个接口或继承同一个类,以保证二者可以互相替代。
         装饰器模式的目的是为了动态地增强对象的功能,他在对象地内部通过一种包装起的方式来实现。装饰器模式中,装饰器类和被装饰对象通常实现同一个接口或继承同一个类,以保证二者可以互相替代。装饰器模式也称为包装器模式。
虽然这两个模式它们实现方式上都是通过继承统一地接口,然后在代理类和装饰者类中通过构造函数引入被代理类和被装饰者类,但是装饰着模式和静态代理模式在设计理念上有着本质的区别。装饰者模式注重于动态地为对象添加新功能,而静态代理模式则注重于对对象访问地控制。
2、使用场景
在Java中,装饰器模式的应用非常广泛,特别是在IO操作中。Java的IO类库就是使用装饰器模式来实现不同的数据流之间的转换和增强的。
2.1 从IO库的设计理解装饰器
例如,我们要打开文件 test.txt 从中读取数据,其中,InputStream 是一个抽象类,FileInputStream 是专门用来读取文件流的子类。BufferedInputStream 是一个支持带缓存功能的数据读取类,可以提高数据读取的效率,具体代码如下:
    InputStream in = new FileInputStream("D:/test.txt");
    InputStream bin = new BufferedInputStream(in);
    byte[] data = new byte[128];
    while (bin.read(data) != -1) {
        //...
    } 
        初看上面的代码可能会觉得麻烦,需要先创建一个FileInputStream对象,然后传递给BufferedInputStream对象来使用。之前会觉得为什么不设计一个 继承FileInputStream对象并且支持缓存的BufferedFileStream类呢? 
         其实如果我们使用继承的方式来实现的话,就需要继续派生出DataFileInputStream、DataPipedInputStream 等类。如果我们还需要既支持缓存、又支持按照基本类型读取数据的类,那就要再继续派生出 BufferedDataFileInputStream、BufferedDataPipedInputStream 等 n 多类,这还只是附加了两个增强功能,如果还需要附加更多功能,那就会导致爆炸,类继承结构变得无比复杂,代码即不好扩展,也不好维护。这就是之前再讲设计原则的时候,不推荐使用继承的原因。
        所以使用装饰器模式来设计IO类会比较好,时间上IO类也确实是这么设计的,但是并不是简单的套用了装饰器的设计模式。
         使用装饰器模式第一个比较特殊的地方:装饰器类和原始类继承同样的父类,这样我们可以对原始类 嵌套 多个装饰器类。
         第二个比较特殊的地方:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。
2.2 mybatis的缓存设计
mybatis中一级缓存 二级缓存,都是使用装饰器模式实现的,具体省略,可以自行查询以下源码。
3、总结
装饰器模式主要解决继承关系过于复杂的问题,通常是通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
对于大多数 添加缓存 的业务场景,核心目的主要就是相增强对象的功能(即增加缓存功能),而不是控制对象的访问,所以装饰器模式可能会更合适。但是比如相对持久层增加一个本地缓存,代理设计模式也是很好的选择。
三、桥接模式
桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,相对于代理模式来说,桥接模式在实际的项目中并没有那么常用,只需要简单了解,见到能认识就行。
1、桥接模式示例
JDBC驱动是桥接模式的经典应用,复习一下JDBC驱动来查询数据库方法,具体代码如下所示:
    // 1.数据库连接的4个基本要素:
    String url = "jdbc:mysql://127.0.0.1:3306/ydlclass?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
    String user = "root";
    String password = "root";
    // 8.0之后名字改了 com.mysql.cj.jdbc.Driver
    String driverName = "com.mysql.cj.jdbc.Driver";
    // 2.实例化Driver,可省略
    // 3.注册驱动,可省略
    // 4.获取连接
    conn = DriverManager.getConnection(url, user, password); 
        如果要把mysql数据库换成Oracle数据库,只需要把驱动com.mysql.cj.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver,同时将url进行替换就可以了。
         在工作中上述配置一般在配饰文件中,一般直接修改配置文件即可。
         不管是改代码还是改配置,在项目中,从一个数据库切换到另一个数据库,都只需要改动很少的代码。或者完全不需要改动代码。这个切换模式的设计要看 com.mysql.cj.jdbc.driver这个类的相关代码。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    /**
     * Construct a new driver and register it with DriverManager
     * @throws SQLException if a database error occurs.
     */
    public Driver() throws SQLException {
    // Required for Class.forName().newInstance()
    }
} 
        结合 com.mysql.jdbc.Driver 的代码实现,可以发现当执行 Class.forName("com.mysql.jdbc.Driver")这条语句的时候,实际上做了两件事情。
         第一件事情是要求JVM查找并加载指定的Driver类。
         第二件事情是执行该类的静态代码,也就是将mysql Driver 注册到DriverManager类中。
         当我们把具体的Driver实现类(比如com.mysql.cj.jdbc.Driver)注册到DriverManager之后,后续所有对JDBC接口的调用,都会委派到对具体的Driver实现类来执行。而Driver实现类都实现了相同的接口(java.sql.Drvier),这也是可以灵活切换Driver的原因。
以下是具体代码:
public class DriverManager {
    // List of registered JDBC drivers
    private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    public static void registerDriver(java.sql.Driver driver,
                                      DriverAction da) throws SQLException {
        /* Register the driver if it has not already been added to our list */
        if (driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
        // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }
    private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
        if (url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }
        println("DriverManager.getConnection(\"" + url + "\")");
        ensureDriversInitialized();
        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
                SQLException reason = null;
        for (DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
            if (isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println(" trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            } else {
                println(" skipping: " + aDriver.driver.getClass().getName());
            }
        }
        // if we got here nobody could connect.
        if (reason != null) {
            println("getConnection failed: " + reason);
            throw reason;
        }
        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }
} 
        桥接模式的定义是“将抽象和实现解耦,让它们独立变化”。在jdbc这个例子中,JDBC本身相当于抽象,注意这里所说的抽象,指的并非是 抽象类 或 接口,而是跟具体的数据库无关的,被抽象出来的一套类库。
         具体的Driver(比如,com.mysql.cj.jdbc.Driver)就相当于 实现。注意 ,这里所说的实现,也并非指 接口的实现类 。而是跟具体数据库相关的一套 类库。JDBC  跟 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC的所有逻辑操作,最终都委托给Driver来执行。
这就是JDBC使用桥接模式的一个例子。通过将抽象的JDBC接口与具体的数据库厂商的实现相分离,我们可以实现对抽象和实现的独立扩展,这种设计模式使得在不修改客户端代码的情况下更换数据库驱动程序成为可能。从而提高了代码的可维护性和可扩展性。
2、桥接模式原理解析
桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象与器实现相分离,以便两者可以独立地进行变化。它通过将抽象的类与实现的类分离,实现了抽象和实现的解耦。
        桥接模式的核心概念:
         1)抽象化:在抽象类中定义抽象业务的接口和一个对实现层次结构的引用。抽象化的主要目的是隐藏实现的细节,以便可以在不影响客户端的情况下更改实现。
         2)实现化:这是一个接口,定了实现抽象化的方法,不同的具体实现类可以有不同的实现方式。
         3)扩展抽象化:这是抽象化的一个具体实现,它定义抽象业务的具体操作。
         4)具体实现化:实现化接口的具体实现类,这些类为抽象业务提供具体的实现
        优点:
         1)抽线与实现分离,可以独立的进行变化
         2)提高了可扩展性
         3)符合单一职责原则,抽象部分专注于抽象化,实现部分专注于具体实现。
        缺点:
         1)增加了系统复杂性
2.1 实现步骤
首先定义具体实现化类的相关api,这里我们可以理解为具体的mysql的实现:
// 可以当做具体的jdbc实现
interface Implementor {
    void operationImpl();
}
// 具体实现化类A,具体实现可以多样化,独立变化
class ConcreteImplementorA implements Implementor {
    @Override
    public void operationImpl() {
        System.out.println("具体实现A");
    }
}
// 具体实现化类B
class ConcreteImplementorB implements Implementor {
    @Override
    public void operationImpl() {
        System.out.println("具体实现B");
    }
} 
接下来编写抽象化类的相关内容,可以理解为jdbc提供的api接口:
abstract class Abstraction {
    protected Implementor implementor;
    // 抽象依赖实现的接口,而不依赖实现本身
    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }
    abstract void operation();
}
// 抽象化类
class RefinedAbstraction extends Abstraction {
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }
    // 请记住这里的方法名字不同,他们不需要实现相同的接口,他可以独立变化
    @Override
    void operation() {
        implementor.operationImpl();
    }
} 
这里展示桥接的过程:
public class BridgePatternDemo {
    public static void main(String[] args) {
        Implementor implementorA = new ConcreteImplementorA();
        Abstraction abstractionA = new RefinedAbstraction(implementorA);
        abstractionA.operation();
        Implementor implementorB = new ConcreteImplementorB();
        Abstraction abstractionB = new RefinedAbstraction(implementorB);
        abstractionB.operation();
    }
} 
        这个例子展示了桥接模式的核心概念,即将抽象与实现分离,这种分离使得我们可以独立的改变抽象类和实现类,提高了代码的可扩展性和可维护性。同时,这种设计也符合了单一职责原则,使得抽象部分和实现部分各自关注自己的核心职责。
         
3、桥接模式的应用举例
3.1 消息通知
        来看一个消息通知系统的例子,这系统需要支持多种通知方式(例如邮件、短信、即时消息等)以及多种通知紧急程度(普通、紧急、非常紧急等)。
         我们可以使用桥接模式将通知方式和通知紧急程度分离,使得它们可以独立的进行变化和扩展。
// 通知方式接口(实现化角色)
interface MessageSender {
    void send(String message);
}
// 邮件通知实现类
class EmailSender implements MessageSender {
    @Override
    public void send(String message) {
        System.out.println("发送邮件通知: " + message);
    }
}
// 短信通知实现类
class SmsSender implements MessageSender {
    @Override
    public void send(String message) {
        System.out.println("发送短信通知: " + message);
    }
}
// 即时消息通知实现类
class InstantMessageSender implements MessageSender {
    @Override
    public void send(String message) {
        System.out.println("发送即时消息通知: " + message);
    }
}
// 抽象通知类(抽象化角色)
abstract class Notification {
    protected MessageSender messageSender;
    public Notification(MessageSender messageSender) {
        this.messageSender = messageSender;
    }
    abstract void notify(String message);
}
// 普通通知子类(扩展抽象化角色)
class NormalNotification extends Notification {
    public NormalNotification(MessageSender messageSender) {
        super(messageSender);
    }
    @Override
    void notify(String message) {
        messageSender.send("普通:" + message);
    }
}
// 紧急通知子类(扩展抽象化角色)
class UrgentNotification extends Notification {
    public UrgentNotification(MessageSender messageSender) {
        super(messageSender);
    }
    @Override
    void notify(String message) {
        messageSender.send("紧急:" + message);
    }
}
// 非常紧急通知子类(扩展抽象化角色)
class CriticalNotification extends Notification {
    public CriticalNotification(MessageSender messageSender) {
        super(messageSender);
    }
    @Override
    void notify(String message) {
        messageSender.send("非常紧急:" + message);
    }
}
public class BridgePatternExample {
    public static void main(String[] args) {
        MessageSender emailSender = new EmailSender();
        MessageSender smsSender = new SmsSender();
        MessageSender instantMessageSender = new InstantMessageSender();
        Notification normalEmailNotification = new
                NormalNotification(emailSender);
        normalEmailNotification.notify("有一个新的任务待处理。");
        Notification urgentSmsNotification = new UrgentNotification(smsSender);
        urgentSmsNotification.notify("系统出现故障,请尽快处理!");
        Notification criticalInstantMessageNotification = new
                CriticalNotification(instantMessageSender);
        criticalInstantMessageNotification.notify("系统崩溃,请立即处理!");
    }
} 
        这个示例中,我们使用桥接模式将通知方式(MessageSender接口以及其实现类)和通知紧急程度(Notification类及其子类)分离,这使得我们可以独立地添加更多的通知方法和通知紧急程度,而不会导致类的数量爆炸性增长。
         以下是如何运行这个示例的步骤:
         1)我们定义了一个MessageSender接口,用于表示通知方式,然后,我们创建了几个实现了MewssageSender接口的具体实现类:EmailSender、SmsSender和InstantMessageSender,它们分别表示通过邮件、短信和及时消息发送通知。
         2)然后,定义了一个抽象类Notification,它持有一个MessageSender对象,Notification类有一个抽象方法notify,用于发送通知。然后,我们创建了几个扩展Notification类的子类:NormalNotification、UrgentNotification 和 CriticalNotification ,它们分别表示普通、紧急 和 非常紧急的通知。
         3)在BridgePatternExample的main方法中,我们创建了 EmailSender 、 SmsSender 和 InstantMessageSender 对象,并将它们与不同紧急程度的通知对象组合,然后,我们调用这些通知对象的norify方法来发送通知。
通过使用桥接模式,我们可以轻松的为消息通知系统添加新的通知方式和通知紧急程度,而无需修改现有的类结构,这种设计增强了代码的可扩展性和可维护性。
3.2 支付方式
看一电商项目中的例子,在这个例子中,需要处理多种支付方式(如信用卡、PayPal、支付宝等)和多种折扣策略(如VIP折扣,新用户折扣、优惠卷等)。
interface PaymentMethod {
    void pay(double amount);
}
// 信用卡支付实现类
class CreditCardPayment implements PaymentMethod {
    @Override
    public void pay(double amount) {
        System.out.println("使用信用卡支付: " + amount);
    }
}
// PayPal支付实现类
class PayPalPayment implements PaymentMethod {
    @Override
    public void pay(double amount) {
        System.out.println("使用PayPal支付: " + amount);
    }
}
// 支付宝支付实现类
class AlipayPayment implements PaymentMethod {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付: " + amount);
    }
}
// 折扣策略接口(抽象化角色)
abstract class DiscountStrategy {
    protected PaymentMethod paymentMethod;
    public DiscountStrategy(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }
    abstract double getDiscountedAmount(double originalAmount);
    public void payWithDiscount(double originalAmount) {
        double discountedAmount = getDiscountedAmount(originalAmount);
        paymentMethod.pay(discountedAmount);
    }
}
// VIP折扣策略子类(扩展抽象化角色)
class VipDiscountStrategy extends DiscountStrategy {
    public VipDiscountStrategy(PaymentMethod paymentMethod) {
        super(paymentMethod);
    }
    @Override
    double getDiscountedAmount(double originalAmount) {
        return originalAmount * 0.9; // VIP用户享有9折优惠
    }
}
// 新用户折扣策略子类(扩展抽象化角色)
class NewUserDiscountStrategy extends DiscountStrategy {
    public NewUserDiscountStrategy(PaymentMethod paymentMethod) {
        super(paymentMethod);
    }
    @Override
    double getDiscountedAmount(double originalAmount) {
        return originalAmount * 0.95; // 新用户享有95折优惠
    }
}
public class ECommerceExample {
    public static void main(String[] args) {
        PaymentMethod creditCardPayment = new CreditCardPayment();
        PaymentMethod payPalPayment = new PayPalPayment();
        PaymentMethod alipayPayment = new AlipayPayment();
        DiscountStrategy vipCreditCardStrategy = new
                VipDiscountStrategy(creditCardPayment);
        vipCreditCardStrategy.payWithDiscount(100);
        DiscountStrategy newUserPayPalStrategy = new
                NewUserDiscountStrategy(payPalPayment);
        newUserPayPalStrategy.payWithDiscount(100);
    }
} 
