【设计模式】八.结构型模式之装饰者模式

装饰者模式

一. 说明

装饰者模式也是结构型模式之一,在使用上类似与套娃, 它是给现有的类扩展一些其他功能,同时又不改变类原本的结构。这看似子类继承也可以做到,但装饰者模式的实现更为独立,它不会入侵类与类之间的关系,装饰者和被装饰者不会相互耦合,独自分离。

二.应用场景

  1. 商场促销活动中多种优惠可以独立使用也可以组合使用,组合使用时使用装饰者模式层层包装优惠策略,逐步计算优惠值。
  2. 在网络游戏中我们的网络通信包是由自定义tcp包信息组成,我们的封包过程可以通过装饰者模式先包装原数据包,再包装tcp包头信息,再包装其他特殊需求的数据。
  3. 在权限拦截器的应用上,我们最初实现了简单的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 ? " 放⾏" : " 拦截"));
    }

在这里插入图片描述
输出结果正确

四. 总结

使用装饰者模式满足单一职责原则,你可以在装饰者类中添加扩展功能而不影响到主类。
装饰者模式可以通过暴露装饰方法进行装饰,也可以通过构造器传入被装饰者进行装饰,一般后者使用较多。
装饰者模式和继承方式需要按需选择,不同场景下两种方式各有优点,不能单纯的认为装饰者模式比继承的方式更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值