• 如何优雅的使用装饰器模式


    一、什么是装饰器模式

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

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

    二、优点

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

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

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

    三、缺点

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

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

    四、使用场景

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

    2、动态增加功能,动态撤销

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

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

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

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

    六、装饰器的简单实现

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

    先定义一个喝水的接口:

    1. public interface Drink {
    2. /**
    3. * 喝水
    4. */
    5. void drink();
    6. }

    写一个接口的实现:

    1. import com.bc.work.service.Drink;
    2. public class DrinkWater implements Drink {
    3. @Override
    4. public void drink() {
    5. System.out.println("喝水");
    6. }
    7. }

    一个简单的装饰器:

    1. import com.bc.work.service.Drink;
    2. public class DrinkDecorator implements Drink {
    3. private final Drink drink;
    4. public DrinkDecorator(Drink drink) {
    5. this.drink = drink;
    6. }
    7. @Override
    8. public void drink() {
    9. System.out.println("先加点儿柠檬片");
    10. drink.drink();
    11. }
    12. }

    开始测试:

    1. import com.bc.work.service.Drink;
    2. import com.bc.work.service.impl.DrinkDecorator;
    3. import com.bc.work.service.impl.DrinkWater;
    4. public class Demo {
    5. public static void main(String[] args) {
    6. Drink drink = new DrinkWater();
    7. drink = new DrinkDecorator(drink);
    8. drink.drink();
    9. }
    10. }

    执行上述代码,其输出结果为:

    1. 先加点儿柠檬片
    2. 喝水

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

    装饰器模式实战

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

    首先创建一个获取数据的接口IDataAccessor:

    1. import java.util.Set;
    2. public interface IDataAccessor {
    3. /**
    4. * 根据部门上级 id 获取所有子集部门
    5. */
    6. Set deptFindAllChildrenByParentId(String parentId);
    7. /**
    8. * 获取数据范围内的部门
    9. */
    10. String deptFindScopeById(int userId);
    11. }

    再创建一个上述接口对应的实现类,注意这里加了@Service,交给Spring处理:

    1. import cn.hutool.core.collection.CollectionUtil;
    2. import com.bc.work.service.IDataAccessor;
    3. import org.springframework.stereotype.Service;
    4. import java.util.*;
    5. @Service
    6. public class ScopeDataAccessorImpl implements IDataAccessor {
    7. private static Map> deptDatas = new HashMap<>();
    8. private static Map userDatas=new HashMap<>();
    9. static {
    10. Set set1=new HashSet<>();
    11. set1.add("1001");
    12. set1.add("1002");
    13. deptDatas.put("1",set1);
    14. Set set2=new HashSet<>();
    15. set2.add("2001");
    16. set2.add("2002");
    17. deptDatas.put("2",set2);
    18. Set set3=new HashSet<>();
    19. set3.add("3001");
    20. set3.add("3002");
    21. deptDatas.put("3",set3);
    22. userDatas.put(1,"1001");
    23. userDatas.put(2,"2002");
    24. userDatas.put(3,"3001");
    25. }
    26. @Override
    27. public Set deptFindAllChildrenByParentId(String parentId) {
    28. Set result = deptDatas.get(parentId);
    29. return CollectionUtil.isEmpty(result)? Collections.emptySet():result;
    30. }
    31. @Override
    32. public String deptFindScopeById(int userId) {
    33. return userDatas.get(userId);
    34. }
    35. }

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

    1. import com.bc.work.service.IDataAccessor;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.cache.annotation.Cacheable;
    4. import java.util.Set;
    5. @Slf4j
    6. public class DataAccessorDecorator implements IDataAccessor {
    7. private final IDataAccessor iDataAccessor;
    8. public DataAccessorDecorator(IDataAccessor iDataAccessor) {
    9. this.iDataAccessor = iDataAccessor;
    10. }
    11. @Cacheable(cacheNames = "dept:parentId", key = "#parentId", sync = true)
    12. @Override
    13. public Set deptFindAllChildrenByParentId(String parentId) {
    14. log.info("从数据库中查询部门信息");
    15. return iDataAccessor.deptFindAllChildrenByParentId(parentId);
    16. }
    17. @Cacheable(cacheNames = "dept:scope:userId", key = "#p0", sync = true)
    18. @Override
    19. public String deptFindScopeById(int userId) {
    20. log.info("从数据库中查询用户信息");
    21. return iDataAccessor.deptFindScopeById(userId);
    22. }
    23. }

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

    1. import com.bc.work.service.IDataAccessor;
    2. import com.bc.work.service.impl.DataAccessorDecorator;
    3. import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. @Configuration
    7. @ConditionalOnBean({IDataAccessor.class})
    8. public class Config {
    9. @Bean
    10. @ConditionalOnBean({IDataAccessor.class})
    11. public DataAccessorDecorator dataAccessorDecorator(IDataAccessor iDataAccessor) {
    12. return new DataAccessorDecorator(iDataAccessor);
    13. }
    14. }

    根据Spring中的事件发布/监听机制来监听部门和员工的变更事件以此来实现动态清除缓存结果:

    1. import lombok.AllArgsConstructor;
    2. import lombok.Data;
    3. @Data
    4. @AllArgsConstructor
    5. public class DepartmentChangeEvent {
    6. /**
    7. * 部门已变更事件的标识符
    8. */
    9. public String flag;
    10. }
    1. import lombok.AllArgsConstructor;
    2. import lombok.Data;
    3. @Data
    4. @AllArgsConstructor
    5. public class UserChangeEvent {
    6. /**
    7. * 用户已变更事件的标识符
    8. */
    9. public String flag;
    10. }
    1. import org.springframework.cache.annotation.CacheEvict;
    2. import org.springframework.stereotype.Component;
    3. @Component
    4. public class DataScopeEvict {
    5. /**
    6. * 清空部门相关缓存
    7. */
    8. @CacheEvict(cacheNames = {"dept:parentId"}, allEntries = true)
    9. public void department() {
    10. }
    11. /**
    12. * 清空用户相关缓存
    13. */
    14. @CacheEvict(cacheNames = {"dept:scope:userId"}, allEntries = true)
    15. public void user() {
    16. }
    17. }
    1. import com.bc.work.service.impl.DataScopeEvict;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.context.event.EventListener;
    5. import org.springframework.stereotype.Component;
    6. @Slf4j
    7. @Component
    8. public class ScopeDataEventListener {
    9. @Autowired
    10. private DataScopeEvict evict;
    11. /**
    12. * 监听部门变更事件
    13. */
    14. @EventListener
    15. public void departmentEvent(DepartmentChangeEvent event) {
    16. log.info("1:增加、2:删除、3:上级部门变更");
    17. evict.department();
    18. }
    19. /**
    20. * 监听user变更事件
    21. */
    22. @EventListener
    23. public void userEvent(UserChangeEvent event) {
    24. log.info("1:删除 2:主部门变更");
    25. evict.user();
    26. }
    27. }

     最后Controller中直接使用装饰器类:

    1. import com.bc.work.listener.DepartmentChangeEvent;
    2. import com.bc.work.listener.UserChangeEvent;
    3. import com.bc.work.service.impl.DataAccessorDecorator;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.context.ApplicationContext;
    6. import org.springframework.stereotype.Controller;
    7. import org.springframework.web.bind.annotation.*;
    8. import java.util.Set;
    9. @Controller
    10. @RequestMapping("/cache")
    11. public class CacheCtroller {
    12. @Autowired
    13. private DataAccessorDecorator dataAccessorDecorator;
    14. @Autowired
    15. private ApplicationContext applicationContext;
    16. @GetMapping("/dept")
    17. @ResponseBody
    18. public String dept(){
    19. String parentId="1";
    20. Set result = dataAccessorDecorator.deptFindAllChildrenByParentId(parentId);
    21. return result.toString();
    22. }
    23. @GetMapping("/user")
    24. @ResponseBody
    25. public String user(){
    26. int userId=1;
    27. String result = dataAccessorDecorator.deptFindScopeById(userId);
    28. return result;
    29. }
    30. @GetMapping("/clearDeptCache")
    31. @ResponseBody
    32. public void clearDeptCache(){
    33. applicationContext.publishEvent(new DepartmentChangeEvent("1"));
    34. System.out.println("部门缓存信息已删除");
    35. }
    36. @GetMapping("/clearUserCache")
    37. @ResponseBody
    38. public void clearUserCache(){
    39. applicationContext.publishEvent(new UserChangeEvent("1"));
    40. System.out.println("用户缓存信息已删除");
    41. }
    42. }

    特别提示:由于使用了@Cacheable注解,在启动类中需要添加@EnableCaching中注解,否则其缓存效果会失效。

    在浏览器中首次访问/cache/user,其页面输出和控制台显示如下:

    从数据库中查询用户信息

    后续再次访问上述地址,浏览器页面显示和上面的一样,但是控制台就没有新的输出信息了,因为@Cache注解的缓存已经生效了。

    接着在浏览器中访问/cache/clearUserCache,其页面输出显示如下:

    控制台的输出内容如下,缓存中的信息已清除:

    1. 1:删除 2:主部门变更
    2. 用户缓存信息已删除

    再次访问/cache/user接口,可以看到数据又是从数据库(伪代码构造的数据库)中获取。 

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

    八、小结

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

  • 相关阅读:
    什么是云计算?什么是IaaS、Paas、Saas?
    阿里技术官最新整理总结号称全网最屌“Redis核心手册”
    (送源码)SSM&MYSQL民宿预订及个性化服务系统04846-计算机毕业设计
    智能水电表对于普通居民来说有哪些好处?
    Windows线程池使用方法
    shell脚本处理日志转化为JsonArray
    ESP32 http 请求
    边云协同架构设计
    解决【无法处理文件xxx,因为它位于 Internet 或受限区域中,或者文件上具有 Web 标记。要想处理这些文件,请删除 Web 标记】问题
    Adobe Premiere基础-编辑素材文件常规操作(脱机文件,替换素材,素材标签和编组,素材启用,便捷调节不透明度,项目打包)(十七)
  • 原文地址:https://blog.csdn.net/y_bccl27/article/details/127402616