重学 Java 设计模式:实战装饰器模式(SSO单点登陆功能扩展,增长拦截用户访问方法范围场景)

做者:小傅哥
博客:https://bugstack.cnhtml

沉淀、分享、成长,让本身和他人都能有所收获!😄

1、前言

对于代码你有编程感受吗java

不少人写代码每每是没有编程感受的,也就是除了能够把功能按照固定的流程编写出流水式的代码外,很难去思考整套功能服务的扩展性和可维护性。尤为是在一些较大型的功能搭建上,比较缺失一些驾驭能力,从而致使最终的代码相对来讲不能作到尽善尽美。web

江洋大盗与江洋大偷spring

两个本想描述同样的意思的词,只因一字只差就让人以为一个是好牛,一个好搞笑。每每咱们去开发编程写代码时也常常将一些不恰当的用法用于业务需求实现中,当却不能意识到。一方面是因为编码很少缺乏较大型项目的实践,另外一方面是不思进取的总在以完成需求为目标缺乏精益求精的工匠精神。编程

书历来不是看的而是用的设计模式

在这个学习资料几乎爆炸的时代,甚至你能够轻易就获取几个T的视频,小手轻轻一点就收藏一堆文章,但却不多去看。学习的过程从不仅是简单的看一遍就能够,对于一些实操性的技术书籍,若是真的但愿学习到知识,那么必定是把这本书用起来而绝对不是看起来。安全

2、开发环境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三个,能够经过关注公众号bugstack虫洞栈,回复源码下载获取(打开获取的连接,找到序号18)
工程 描述
itstack-demo-design-9-00 场景模拟工程;模拟单点登陆类
itstack-demo-design-9-01 使用一坨代码实现业务需求
itstack-demo-design-9-02 经过设计模式优化改造代码,产生对比性从而学习

3、装饰器模式介绍

装饰器模式,图片来自 refactoringguru.cn

初看上图感受装饰器模式有点像俄罗斯套娃、某众汽车🚕,而装饰器的核心就是再不改原有类的基础上给类新增功能。不改变原有类,可能有的小伙伴会想到继承、AOP切面,固然这些方式均可以实现,可是使用装饰器模式会是另一种思路更为灵活,能够避免继承致使的子类过多,也能够避免AOP带来的复杂性。微信

你熟悉的场景不少用到装饰器模式cookie

new BufferedReader(new FileReader(""));,这段代码你是否熟悉,相信学习java开发到字节流、字符流、文件流的内容时都见到了这样的代码,一层嵌套一层,一层嵌套一层,字节流转字符流等等,而这样方式的使用就是装饰器模式的一种体现。并发

4、案例场景模拟

场景模拟;单点登陆功能扩展

在本案例中咱们模拟一个单点登陆功能扩充的场景

通常在业务开发的初期,每每内部的ERP使用只须要判断帐户验证便可,验证经过后便可访问ERP的全部资源。但随着业务的不断发展,团队里开始出现专门的运营人员、营销人员、数据人员,每一个人员对于ERP的使用需求不一样,有些须要建立活动,有些只是查看数据。同时为了保证数据的安全性,不会让每一个用户都有最高的权限。

那么以往使用的SSO是一个组件化通用的服务,不能在里面添加须要的用户访问验证功能。这个时候咱们就可使用装饰器模式,扩充原有的单点登陆服务。但同时也保证原有功能不受破坏,能够继续使用。

1. 场景模拟工程

itstack-demo-design-9-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── HandlerInterceptor.java
                └── SsoInterceptor.java
  • 这里模拟的是spring中的类:HandlerInterceptor,实现起接口功能SsoInterceptor模拟的单点登陆拦截服务。
  • 为了不引入太多spring的内容影响对设计模式的阅读,这里使用了同名的类和方法,尽量减小外部的依赖。

2. 场景简述

2.1 模拟Spring的HandlerInterceptor

public interface HandlerInterceptor {

    boolean preHandle(String request, String response, Object handler);

}
  • 实际的单点登陆开发会基于;org.springframework.web.servlet.HandlerInterceptor 实现。

2.2 模拟单点登陆功能

public class SsoInterceptor implements HandlerInterceptor{

    public boolean preHandle(String request, String response, Object handler) {
        // 模拟获取cookie
        String ticket = request.substring(1, 8);
        // 模拟校验
        return ticket.equals("success");
    }

}
  • 这里的模拟实现很是简单只是截取字符串,实际使用须要从HttpServletRequest request对象中获取cookie信息,解析ticket值作校验。
  • 在返回的里面也很是简单,只要获取到了success就认为是容许登陆。

5、用一坨坨代码实现

此场景大多数实现的方式都会采用继承类

继承类的实现方式也是一个比较通用的方式,经过继承后重写方法,并发将本身的逻辑覆盖进去。若是是一些简单的场景且不须要不断维护和扩展的,此类实现并不会有什么,也不会致使子类过多。

1. 工程结构

itstack-demo-design-9-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── LoginSsoDecorator.java
  • 以上工程结构很是简单,只是经过 LoginSsoDecorator 继承 SsoInterceptor,重写方法功能。

2. 代码实现

public class LoginSsoDecorator extends SsoInterceptor {

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        // 模拟获取cookie
        String ticket = request.substring(1, 8);
        // 模拟校验
        boolean success = ticket.equals("success");

        if (!success) return false;

        String userId = request.substring(9);
        String method = authMap.get(userId);

        // 模拟方法校验
        return "queryUserInfo".equals(method);
    }

}
  • 以上这部分经过继承重写方法,将我的可访问哪些方法的功能添加到方法中。
  • 以上看着代码还算比较清晰,但若是是比较复杂的业务流程代码,就会很混乱。

3. 测试验证

3.1 编写测试类

@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator();
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登陆校验:" + request + (success ? " 放行" : " 拦截"));
}
  • 这里模拟的至关于登陆过程当中的校验操做,判断用户是否可登陆以及是否可访问方法。

3.2 测试结果

登陆校验:1successhuahua 拦截

Process finished with exit code 0
  • 从测试结果来看知足咱们的预期,已经作了拦截。若是你在学习的过程当中,能够尝试模拟单点登陆并继承扩展功能。

6、装饰器模式重构代码

接下来使用装饰器模式来进行代码优化,也算是一次很小的重构。

装饰器主要解决的是直接继承下因功能的不断横向扩展致使子类膨胀的问题,而是用装饰器模式后就会比直接继承显得更加灵活同时这样也就再也不须要考虑子类的维护。

在装饰器模式中有四个比较重要点抽象出来的点;

  1. 抽象构件角色(Component) - 定义抽象接口
  2. 具体构件角色(ConcreteComponent) - 实现抽象接口,能够是一组
  3. 装饰角色(Decorator) - 定义抽象类并继承接口中的方法,保证一致性
  4. 具体装饰角色(ConcreteDecorator) - 扩展装饰具体的实现逻辑

经过以上这四项来实现装饰器模式,主要核心内容会体如今抽象类的定义和实现上。

1. 工程结构

itstack-demo-design-9-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── LoginSsoDecorator.java
                └── SsoDecorator.java

装饰器模式模型结构

装饰器模式模型结构

  • 以上是一个装饰器实现的类图结构,重点的类是SsoDecorator,这个类是一个抽象类主要完成了对接口HandlerInterceptor继承。
  • 当装饰角色继承接口后会提供构造函数,入参就是继承的接口实现类便可,这样就能够很方便的扩展出不一样功能组件。

2. 代码实现

2.1 抽象类装饰角色

public abstract class SsoDecorator implements HandlerInterceptor {

    private HandlerInterceptor handlerInterceptor;

    private SsoDecorator(){}

    public SsoDecorator(HandlerInterceptor handlerInterceptor) {
        this.handlerInterceptor = handlerInterceptor;
    }

    public boolean preHandle(String request, String response, Object handler) {
        return handlerInterceptor.preHandle(request, response, handler);
    }

}
  • 在装饰类中有两个重点的地方是;1)继承了处理接口、2)提供了构造函数、3)覆盖了方法preHandle
  • 以上三个点是装饰器模式的核心处理部分,这样能够踢掉对子类继承的方式实现逻辑功能扩展。

2.2 装饰角色逻辑实现

public class LoginSsoDecorator extends SsoDecorator {

    private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    public LoginSsoDecorator(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;
        String userId = request.substring(8);
        String method = authMap.get(userId);
        logger.info("模拟单点登陆方法访问拦截校验:{} {}", userId, method);
        // 模拟方法校验
        return "queryUserInfo".equals(method);
    }
}
  • 在具体的装饰类实现中,继承了装饰类SsoDecorator,那么如今就能够扩展方法;preHandle
  • preHandle的实现中能够看到,这里只关心扩展部分的功能,同时不会影响原有类的核心服务,也不会由于使用继承方式而致使的多余子类,增长了总体的灵活性。

3. 测试验证

3.1 编写测试类

@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登陆校验:" + request + (success ? " 放行" : " 拦截"));
}
  • 这里测试了对装饰器模式的使用,经过透传原有单点登陆类new SsoInterceptor(),传递给装饰器,让装饰器能够执行扩充的功能。
  • 同时对于传递者和装饰器均可以是多组的,在一些实际的业务开发中,每每也是因为太多类型的子类实现而致使不易于维护,从而使用装饰器模式替代。

3.2 测试结果

23:50:50.796 [main] INFO  o.i.demo.design.LoginSsoDecorator - 模拟单点登陆方法访问拦截校验:huahua queryUserInfo
登陆校验:1successhuahua 放行

Process finished with exit code 0
  • 结果符合预期,扩展了对方法拦截的校验性。
  • 若是你在学习的过程当中有用到过单点登录,那么能够适当在里面进行扩展装饰器模式进行学习使用。
  • 另外,还有一种场景也可使用装饰器。例如;你以前使用某个实现某个接口接收单个消息,但因为外部的升级变为发送list集合消息,但你又不但愿全部的代码类都去修改这部分逻辑。那么可使用装饰器模式进行适配list集合,给使用者依然是for循环后的单个消息。

7、总结

  • 使用装饰器模式知足单一职责原则,你能够在本身的装饰类中完成功能逻辑的扩展,而不影响主类,同时能够按需在运行时添加和删除这部分逻辑。另外装饰器模式与继承父类重写方法,在某些时候须要按需选择,并不必定某一个就是最好。
  • 装饰器实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口能够经过构造函数传递其实现类,由此增长扩展性并重写方法里能够实现此部分父类实现的功能。
  • 就像夏天热你穿短裤,冬天冷你穿棉裤,雨天挨浇你穿雨衣同样,你的根本自己没有被改变,而你的需求却被不一样的装饰而实现。生活中每每比比皆是设计,当你能够融合这部分活灵活现的例子到代码实现中,每每会创造出更加优雅的实现方式。

8、推荐阅读

相关文章
相关标签/搜索