装饰者模式
一. 说明
装饰者模式也是结构型模式之一,在使用上类似与套娃, 它是给现有的类扩展一些其他功能,同时又不改变类原本的结构
。这看似子类继承也可以做到,但装饰者模式的实现更为独立,它不会入侵类与类之间的关系,装饰者和被装饰者不会相互耦合,独自分离。
二.应用场景
- 商场促销活动中多种优惠可以独立使用也可以组合使用,组合使用时使用装饰者模式层层包装优惠策略,逐步计算优惠值。
- 在网络游戏中我们的网络通信包是由自定义tcp包信息组成,我们的封包过程可以通过装饰者模式先包装原数据包,再包装tcp包头信息,再包装其他特殊需求的数据。
- 在权限拦截器的应用上,我们最初实现了简单的token校验,后面需要加入角色检验,就可以用装饰者模式扩展角色校验的功能。
三.代码示例
以权限拦截器为例,我们先在拦截器中实现token校验的逻辑
public interface HandlerInterceptor {
boolean preHandle(String request, String response, Object handler);
}
public class AuthInterceptor implements HandlerInterceptor{
//使用本地map模拟redis存储token
private static final Map<String, String> tokenCache = new HashMap<>();
static {
tokenCache.put("abcde", "100001");
tokenCache.put("fghit", "100002");
tokenCache.put("rsxyz", "100003");
}
@Override
public boolean preHandle(String request, String response, Object handler) {
// 模拟获取token
String token = request.substring(0, 5);
//模拟校验token
if(!tokenCache.containsKey(token)){
return false;
}
//模拟将uid放入请求头
request = tokenCache.get(token);
return true;
}
}
我们再往拦截器中加入角色校验的步骤
public class AuthInterceptor implements HandlerInterceptor {
//使用本地map模拟redis存储token
private static final Map<String, String> tokenCache = new HashMap<>();
//使用本地map模拟redis存储权限值
private static final Map<String, List<String>> roleCache = new HashMap<>();
static {
tokenCache.put("abcde", "100001");
tokenCache.put("fghit", "100002");
tokenCache.put("rsxyz", "100003");
roleCache.put("100001", Arrays.asList("/user", "/product", "/order"));
roleCache.put("100002", Arrays.asList("/user"));
roleCache.put("100003", new ArrayList<>());
}
@Override
public boolean preHandle(String request, String response, Object handler) {
// 模拟获取token
String token = request.substring(0, 5);
//模拟请求path
String path = request.substring(5, request.length());
//模拟校验token
if (!tokenCache.containsKey(token)) {
return false;
}
String uid = tokenCache.get(token);
//模拟校验角色
if (!checkRole(uid, path)) {
return false;
}
//模拟将uid放入请求头
request = uid;
return true;
}
/**
* 模拟校验角色
*/
private boolean checkRole(String uid, String path) {
List<String> roles = roleCache.get(uid);
return roles.contains(path);
}
}
测试输出结果
public static void main(String[] args) {
AuthInterceptor interceptor = new AuthInterceptor();
String request = "abcde/user";
boolean success = interceptor.preHandle(request, "", null);
System.out.println("权限校验结果:" + request + (success ? " 放⾏" : " 拦截"));
request = "rsxyz/user";
success = interceptor.preHandle(request, "", null);
System.out.println("权限校验结果:" + request + (success ? " 放⾏" : " 拦截"));
}
这里我们可以看到,虽然代码功能已经实现了,但整个拦截器的代码非常臃肿,如果后续还需要添加一些功能进去,一个是会影响到已有的代码逻辑,再者代码会变得非常难以维护。
现在我们利用装饰者模式进行改造, 保留拦截器接口,扩充一个权限拦截器的类(表示原始权限拦截器),建立一个抽象装饰类,该类继承了拦截器接口,提供包装构造器,重写拦截器方法。
public class AbstractDecorator implements HandlerInterceptor {
private HandlerInterceptor handlerInterceptor;
public AbstractDecorator() {
}
public AbstractDecorator(HandlerInterceptor handlerInterceptor) {
this.handlerInterceptor = handlerInterceptor;
}
@Override
public boolean preHandle(String request, String response, Object handler) {
return handlerInterceptor.preHandle(request, response, handler);
}
}
public class AuthInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(String request, String response, Object handler) {
//原始权限拦截器默认全部放行,由上层装饰者进行拦截业务
return true;
}
}
再实现token校验的功能,写一个装饰者继承抽象装饰类
//这个装饰者只处理token检验的逻辑
public class TokenInterceptor extends AbstractDecorator {
//使用本地map模拟redis存储token
public static final Map<String, String> tokenCache = new HashMap<>();
static {
tokenCache.put("abcde", "100001");
tokenCache.put("fghit", "100002");
tokenCache.put("rsxyz", "100003");
}
public TokenInterceptor(HandlerInterceptor handlerInterceptor) {
super(handlerInterceptor);
}
@Override
public boolean preHandle(String request, String response, Object handler) {
boolean success = super.preHandle(request, response, handler);
if (!success) {
return false;
}
// 模拟获取token
String token = request.substring(0, 5);
//模拟校验token
if (!tokenCache.containsKey(token)) {
return false;
}
//需要有将uid放入请求头的逻辑,这里无法模拟
return true;
}
}
再实现权限值校验的功能,继续写一个装饰者继承抽象装饰类
//这个装饰者只处理权限值检验的逻辑
public class RoleInterceptor extends AbstractDecorator {
//使用本地map模拟redis存储权限值
private static final Map<String, List<String>> roleCache = new HashMap<>();
static {
roleCache.put("100001", Arrays.asList("/user", "/product", "/order"));
roleCache.put("100002", Arrays.asList("/user"));
roleCache.put("100003", new ArrayList<>());
}
public RoleInterceptor(HandlerInterceptor handlerInterceptor) {
super(handlerInterceptor);
}
@Override
public boolean preHandle(String request, String response, Object handler) {
//先校验父类
boolean success = super.preHandle(request, response, handler);
if (!success) {
return false;
}
//模拟从请求头获取uid
String uid = TokenInterceptor.tokenCache.get(request.substring(0, 5));
String path = request.substring(5, request.length());
//模拟校验权限值
List<String> roles = roleCache.get(uid);
if (!roles.contains(path)) {
return false;
}
return true;
}
}
编写测试代码输出结果
public static void main(String[] args) {
RoleInterceptor roleInterceptor = new RoleInterceptor(new TokenInterceptor(new CheckInterceptor()));
String request = "abcde/user";
boolean success = roleInterceptor .preHandle(request, "", null);
System.out.println("权限校验结果:" + request + (success ? " 放⾏" : " 拦截"));
request = "rsxyz/user";
success = roleInterceptor .preHandle(request, "", null);
System.out.println("权限校验结果:" + request + (success ? " 放⾏" : " 拦截"));
}
输出结果正确
四. 总结
使用装饰者模式满足单一职责原则,你可以在装饰者类中添加扩展功能而不影响到主类。
装饰者模式可以通过暴露装饰方法进行装饰,也可以通过构造器传入被装饰者进行装饰,一般后者使用较多。
装饰者模式和继承方式需要按需选择,不同场景下两种方式各有优点,不能单纯的认为装饰者模式比继承的方式更好。