详解Java如何优雅的使用装饰器模式

 更新时间:2022年09月14日 08:27:19   作者:指北君  
装饰器设计模式大家肯定都听说过,但是有没有使用过呢,今天本君就跟大家分享一下装饰器模式应该如何使用,感兴趣的小伙伴可以学习一下

什么是装饰器模式

装饰器模式(Decorator Pattern): 在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责;

感觉和继承如出一辙,不改变父类,子类可拓展功能;

优点

装饰类和被装饰类可以独立发展,不会相互耦合

相比于继承,更加的轻便、灵活

可以动态扩展一个实现类的功能,不必修改原本代码

缺点

会产生很多的装饰类,增加了系统的复杂性。

这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

使用场景

对已有的目标功能存在不足,需要增强时,扩展类的功能。

动态增加功能,动态撤销

装饰器模式和代理模式的区别

代理是全权代理,目标根本不对外,全部由代理类来完成;装饰是增强,是辅助,目标仍然可以自行对外提供服务,装饰器只起增强作用。

装饰器模式强调的是:增强、新增行为;代理模式强调的是:对代理的对象施加控制,但不对对象本身的功能进行增强

装饰器模式:生效的对象还是原本的对象;代理模式:生效的是新的对象(代理对象)

装饰器和代理的区别

装饰器的简单实现

场景:天气太热了,喝点儿冰水解解暑;加点儿柠檬片,让果汁好喝点儿

先定义一个喝水的接口

public interface Drink {
    /**
     * 喝水
     */
    void drink();
}

写一个接口的实现

public class DrinkWater implements Drink {

    @Override
    public void drink() {
        System.out.println("喝水");
    }

}

一个简单的装饰器

public class DrinkDecorator implements Drink {

    private final Drink drink;

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

    @Override
    public void drink() {
        System.out.println("先加点儿柠檬片");
        drink.drink();
    }

}

开始测试

public class DrinkMain {
    public static void main(String[] args) {
        Drink drink = new DrinkWater();
        drink = new DrinkDecorator(drink);
        drink.drink();
    }
}

运行结果

先加点儿柠檬片
喝水

一个简单的装饰器模式例子就写完了;当然这种例子在实际项目中肯定是用不到的,这里只是先了解一下装饰器模式

装饰器模式实战

场景: 项目一期开发的时候,并没有给鉴权部分设置缓存;二期开发考虑到性能问题,想要给鉴权部分加上缓存,这里就选择了使用装饰器模式进行处理;

这里使用的缓存是spring的 spring-cache,不了解没关系,知道几个注解什么意思就行

@Cacheable 表示要对方法返回值进行缓存

@CacheEvict 删除缓存注解

为了简洁,以下代码均为伪代码

首先,需要一个权限的接口和实现类

public interface IDataAccessor {
    /**
     * 根据部门上级 id 获取所有子集部门
     */
    Set<Long> deptFindAllChildrenByParentIds(Collection<Long> parentIds);

    /**
     * 获取数据范围内的部门
     */
    Set<Long> deptFindScopeById(Long userId);

实现类(注意这里加了@Service, 交给spring处理)

@Service
public class ScopeDataAccessorImpl implements IDataAccessor {
    @Autowired
    private IDepartmentService departmentService;
    
    @Autowired
    private INodeScopeService nodeScopeService;

    @Override
    public Set<Long> deptFindAllChildrenByParentIds(Collection<Long> parentIds) {
        Set<Long> result = new HashSet<>();
        departmentService.departmentChildren(parentIds, result);
        return result;
    }
    
    @Override
    public Set<Long> deptFindScopeById(Long userId) {
        return nodeScopeService.deptFindScopeById(userId);
    }
}

接下来就是对之前的代码进行装饰,定义一个装饰器的实现类

(这个类没有 @Component, 没有直接交给spring管理;加了注解会报错:找到了2个bean)

public class DataAccessorDecorator implements IDataAccessor {
    private final IDataAccessor iDataAccessor;

    public DataAccessorDecorator(IDataAccessor iDataAccessor) {
        this.iDataAccessor = iDataAccessor;
    }

    @Cacheable(cacheNames = "dept:parentId", key = "#p0", sync = true)
    @Override
    public Set<Long> deptFindAllChildrenByParentIds(Collection<Long> parentIds) {
        return iDataAccessor.deptFindAllChildrenByParentIds(parentIds);
    }

    @Cacheable(cacheNames = "dept:scope:userId", key = "#p0", sync = true)
    @Override
    public Set<Long> deptFindScopeById(Long userId) {
        return iDataAccessor.deptFindScopeById(nodeId,userId);
    }
}

接下来还需要将这个装饰器的类注册到spring中

@Configuration
@ConditionalOnBean({IDataAccessor.class})
public class Config {
    
    @Bean
    @ConditionalOnBean({IDataAccessor.class})
    public DataAccessorDecorator dataAccessorDecorator(IDataAccessor iDataAccessor) {
        return new DataAccessorDecorator(iDataAccessor);
    }
}

根据业务,维护缓存更新;这里使用的监听部门和员工的变更事件

@Component
public class DataScopeEvict {

    /**
     * 清空部门相关缓存
     */
    @CacheEvict(cacheNames = {"dept:parentId"}, allEntries = true)
    public void department() {
    }

    /**
     * 清空用户相关缓存
     */
    @CacheEvict(cacheNames = {"dept:scope:userId"}, allEntries = true)
    public void user() {
    }
}
@Component
public class ScopeDataEventListener {
    @Autowired
    private DataScopeEvict evict;
    
 /**
     * 监听部门变更事件
     */
    @EventListener
    public void departmentEvent(DepartmentChangeEvent event) {
        // 1 增加 2 删除 3 上级部门变更
        evict.department();
    }

    /**
     * 监听user变更事件
     */
    @EventListener
    public void userEvent(UserChangeEvent event) {
        // 2 删除 3 主部门变更
        if (event.getType().equals(2) || event.getType().equals(3)) {
            evict.user();
        }
    }
}

一切准备就绪,使用的时候直接使用装饰器类就好了

@Service
public class UserService {
    
    @Autowired
    DataAccessorDecorator scopeDataAccessor;

    
    public Set<Long> deptFindAllChildrenByParentIds(Collection<Long> parentIds) {
        return scopeDataAccessor.deptFindAllChildrenByParentIds(parentIds);
    }
    
    
    public Set<Long> deptFindScopeById(Long userId) {
        return scopeDataAccessor.deptFindScopeById(userId);
    }
    
}

以上就是一个将装饰器模式应用到实际项目的例子;

在这个例子中,使用装饰器模式增强了原本的代码,不修改原本的代码,原本的代码也能正确提供服务,只不过没有使用缓存;只要方法名命名一致,只需修改注入的字段就可以升级完成,升级成本还是很低的。

这波使用装饰器模式加缓存的操作写到项目中,直接让你的代码 B ge pull full

小结

虽然使用装饰器模式看起来B格高,但还是要注意自己项目的场景,选择适合的方式解决问题。

以上就是详解Java如何优雅的使用装饰器模式的详细内容,更多关于Java装饰器模式的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot中的FailureAnalyzer使用详解

    SpringBoot中的FailureAnalyzer使用详解

    这篇文章主要介绍了SpringBoot中的FailureAnalyzer使用详解,Spring Boot的FailureAnalyzer是一个接口,它用于在Spring Boot应用启动失败时提供有关错误的详细信息,这对于开发者来说非常有用,因为它可以帮助我们快速识别问题并找到解决方案,需要的朋友可以参考下
    2023-12-12
  • springboot跨域过滤器fetch react Response to preflight request doesn‘t pass access control check问题

    springboot跨域过滤器fetch react Response to p

    这篇文章主要介绍了springboot跨域过滤器fetch react Response to preflight request doesn‘t pass access control check问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Java实题演练二叉搜索树与双向链表分析

    Java实题演练二叉搜索树与双向链表分析

    这篇文章主要介绍了Java二叉搜索树与双向链表,总的来说这并不是一道难题,那为什么要拿出这道题介绍?拿出这道题真正想要传达的是解题的思路,以及不断优化探寻最优解的过程。希望通过这道题能给你带来一种解题优化的思路
    2022-12-12
  • java hutool工具类处理JSON的使用方法

    java hutool工具类处理JSON的使用方法

    hutool是一个java基础工具类,该工具类经过长期的发展,API已经非常齐全,下面这篇文章主要给大家介绍了关于java hutool工具类处理JSON的使用方法,需要的朋友可以参考下
    2024-04-04
  • Spring BeanDefinition父子关系示例解析

    Spring BeanDefinition父子关系示例解析

    这篇文章主要为大家介绍了Spring BeanDefinition父子关系示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Java依赖倒转原则_动力节点Java学院整理

    Java依赖倒转原则_动力节点Java学院整理

    这篇文章主要介绍了Java依赖倒转原则的定义及问题由来解决方案,感兴趣的朋友一起看看吧
    2017-08-08
  • java 使用BeanFactory实现service与dao层解耦合详解

    java 使用BeanFactory实现service与dao层解耦合详解

    这篇文章主要介绍了java 使用BeanFactory实现service与dao层解耦合详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java调用构造函数和方法及使用详解

    Java调用构造函数和方法及使用详解

    在Java编程中,构造函数用于初始化新创建的对象,而方法则用于执行对象的行为,构造函数在使用new关键字创建类实例时自动调用,没有返回类型,并且名称与类名相同,本文通过示例详细介绍了如何在Java中使用构造函数和方法,感兴趣的朋友一起看看吧
    2024-10-10
  • linux下执行java程序的sh脚本分享

    linux下执行java程序的sh脚本分享

    这篇文章主要介绍了linux下执行java程序的sh脚本,仅供参考,但是设置的时候环境变量是最重要的,我就是环境变量一直不对,总是按网上查到的来,不明白怎么回事,才一直出错,其实环境变量就是你要执行的java程序所在的位置
    2014-09-09
  • Java编写简单计算器的完整实现过程

    Java编写简单计算器的完整实现过程

    这篇文章主要给大家介绍了关于Java编写简单计算器的完整实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12

最新评论