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

本文详细介绍了装饰器模式的概念、优点和缺点,通过喝水的例子展示了装饰器模式的基本用法。接着,结合实际项目中的鉴权缓存场景,展示了如何使用装饰器模式动态添加缓存功能,利用Spring Cache实现数据访问的性能优化。同时,通过事件监听机制实现实时清除缓存,确保数据一致性。最后,提醒读者在选择设计模式时要根据项目需求谨慎选择。

一、什么是装饰器模式

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

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

二、优点

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

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

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

三、缺点

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

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

四、使用场景

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

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

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

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

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

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

六、装饰器的简单实现

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

先定义一个喝水的接口:

public interface Drink {

    /**
     * 喝水
     */
    void drink();
}

写一个接口的实现:

import com.bc.work.service.Drink;

public class DrinkWater implements Drink {

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

一个简单的装饰器:

import com.bc.work.service.Drink;

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();
    }
}

开始测试:

import com.bc.work.service.Drink;
import com.bc.work.service.impl.DrinkDecorator;
import com.bc.work.service.impl.DrinkWater;

public class Demo {

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

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

先加点儿柠檬片
喝水

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

装饰器模式实战

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

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

import java.util.Set;

public interface IDataAccessor {

    /**
     * 根据部门上级 id 获取所有子集部门
     */
    Set<String> deptFindAllChildrenByParentId(String parentId);

    /**
     * 获取数据范围内的部门
     */
    String deptFindScopeById(int userId);
}

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

import cn.hutool.core.collection.CollectionUtil;
import com.bc.work.service.IDataAccessor;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class ScopeDataAccessorImpl implements IDataAccessor {

    private static Map<String,Set<String>> deptDatas = new HashMap<>();
    private static Map<Integer,String> userDatas=new HashMap<>();
    static {
        Set<String> set1=new HashSet<>();
        set1.add("1001");
        set1.add("1002");
        deptDatas.put("1",set1);
        Set<String> set2=new HashSet<>();
        set2.add("2001");
        set2.add("2002");
        deptDatas.put("2",set2);
        Set<String> set3=new HashSet<>();
        set3.add("3001");
        set3.add("3002");
        deptDatas.put("3",set3);

        userDatas.put(1,"1001");
        userDatas.put(2,"2002");
        userDatas.put(3,"3001");
    }

    @Override
    public Set<String> deptFindAllChildrenByParentId(String parentId) {
        Set<String> result = deptDatas.get(parentId);
        return CollectionUtil.isEmpty(result)? Collections.emptySet():result;
    }

    @Override
    public String deptFindScopeById(int userId) {
        return userDatas.get(userId);
    }
}

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

import com.bc.work.service.IDataAccessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import java.util.Set;

@Slf4j
public class DataAccessorDecorator implements IDataAccessor {

    private final IDataAccessor iDataAccessor;

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

    @Cacheable(cacheNames = "dept:parentId", key = "#parentId", sync = true)
    @Override
    public Set<String> deptFindAllChildrenByParentId(String parentId) {
        log.info("从数据库中查询部门信息");
        return iDataAccessor.deptFindAllChildrenByParentId(parentId);
    }

    @Cacheable(cacheNames = "dept:scope:userId", key = "#p0", sync = true)
    @Override
    public String deptFindScopeById(int userId) {
        log.info("从数据库中查询用户信息");
        return iDataAccessor.deptFindScopeById(userId);
    }
}

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

import com.bc.work.service.IDataAccessor;
import com.bc.work.service.impl.DataAccessorDecorator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnBean({IDataAccessor.class})
public class Config {

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

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

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class DepartmentChangeEvent {

    /**
     * 部门已变更事件的标识符
     */
    public String flag;
}
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class UserChangeEvent {

    /**
     * 用户已变更事件的标识符
     */
    public String flag;
}
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Component;

@Component
public class DataScopeEvict {

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

    }

    /**
     * 清空用户相关缓存
     */
    @CacheEvict(cacheNames = {"dept:scope:userId"}, allEntries = true)
    public void user() {

    }
}
import com.bc.work.service.impl.DataScopeEvict;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class ScopeDataEventListener {

    @Autowired
    private DataScopeEvict evict;

    /**
     * 监听部门变更事件
     */
    @EventListener
    public void departmentEvent(DepartmentChangeEvent event) {
        log.info("1:增加、2:删除、3:上级部门变更");
        evict.department();
    }

    /**
     * 监听user变更事件
     */
    @EventListener
    public void userEvent(UserChangeEvent event) {
        log.info("1:删除 2:主部门变更");
        evict.user();
    }
}

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

import com.bc.work.listener.DepartmentChangeEvent;
import com.bc.work.listener.UserChangeEvent;
import com.bc.work.service.impl.DataAccessorDecorator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Set;

@Controller
@RequestMapping("/cache")
public class CacheCtroller {

    @Autowired
    private DataAccessorDecorator dataAccessorDecorator;
    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/dept")
    @ResponseBody
    public String dept(){
        String parentId="1";
        Set<String> result = dataAccessorDecorator.deptFindAllChildrenByParentId(parentId);
        return result.toString();
    }

    @GetMapping("/user")
    @ResponseBody
    public String user(){
        int userId=1;
        String result = dataAccessorDecorator.deptFindScopeById(userId);
        return result;
    }

    @GetMapping("/clearDeptCache")
    @ResponseBody
    public void clearDeptCache(){
        applicationContext.publishEvent(new DepartmentChangeEvent("1"));
        System.out.println("部门缓存信息已删除");
    }

    @GetMapping("/clearUserCache")
    @ResponseBody
    public void clearUserCache(){
        applicationContext.publishEvent(new UserChangeEvent("1"));
        System.out.println("用户缓存信息已删除");
    }
}

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

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

从数据库中查询用户信息

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

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

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

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

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

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

八、小结

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

MATLAB代码实现了一个基于多种智能优化算法优化RBF神经网络的回归预测模型,其核心是通过智能优化算法自动寻找最优的RBF扩展参数(spread),以提升预测精度。 1.主要功能 多算法优化RBF网络:使用多种智能优化算法优化RBF神经网络的核心参数spread。 回归预测:对输入特征进行回归预测,适用于连续值输出问题。 性能对比:对比不同优化算法在训练集和测试集上的预测性能,绘制适应度曲线、预测对比图、误差指标柱状图等。 2.算法步骤 数据准备:导入数据,随机打乱,划分训练集和测试集(默认7:3)。 数据归一化:使用mapminmax将输入和输出归一化到[0,1]区间。 标准RBF建模:使用固定spread=100建立基准RBF模型。 智能优化循环: 调用优化算法(从指定文件夹中读取算法文件)优化spread参数。 使用优化后的spread重新训练RBF网络。 评估预测结果,保存性能指标。 结果可视化: 绘制适应度曲线、训练集/测试集预测对比图。 绘制误差指标(MAE、RMSE、MAPE、MBE)柱状图。 十种智能优化算法分别是: GWO:灰狼算法 HBA:蜜獾算法 IAO:改进天鹰优化算法,改进①:Tent混沌映射种群初始化,改进②:自适应权重 MFO:飞蛾扑火算法 MPA:海洋捕食者算法 NGO:北方苍鹰算法 OOA:鱼鹰优化算法 RTH:红尾鹰算法 WOA:鲸鱼算法 ZOA:斑马算法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值