一、什么是装饰器模式
装饰器模式(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格高,但还是要注意自己项目的场景,选择适合的方式解决问题。
本文详细介绍了装饰器模式的概念、优点和缺点,通过喝水的例子展示了装饰器模式的基本用法。接着,结合实际项目中的鉴权缓存场景,展示了如何使用装饰器模式动态添加缓存功能,利用Spring Cache实现数据访问的性能优化。同时,通过事件监听机制实现实时清除缓存,确保数据一致性。最后,提醒读者在选择设计模式时要根据项目需求谨慎选择。
220

被折叠的 条评论
为什么被折叠?



