设计模式介绍
装饰器模式就像俄罗斯套娃,它的核心是在不改变原有类的基础上给类新增功能。对于不改变原有类,可能有的人会想到集成、AOP切面,虽然这些方式都可以实现,但是使用装饰器模式使另外一种更灵活的思路,能够避免继承导致的子类过多问题,也可以避免AOP带来的复杂性问题。
装饰器模式简单使用
场景:单点登录场景模拟
在业务开发的初期,往往运营人员使用的是ERP系统,只需要登录验证即可,验证通过后即可访问ERP的所有资源。但随着业务的不断发展,团队里开始出现专门的运营人员、营销人员和数据人员,每类人员对ERP的使用需求不同,有些需要创建活动,有些只是查看数据。同时,为了保证数据的安全,不会让每位运营人员都有最高的权限。
那么,以往使用的SSO是一个组件化通用的服务,不能在里面添加需要的用户访问验证功能。这时就可以使用装饰器模式扩充原有的单点登录服务,同时也保证原有功能不受破坏,可以继续使用。
我们使用的是模拟的Spring类的HandlerInterceptor,实现接口SsoInterceptor模拟的单点登录拦截服务。为了避免引入太多的Spring内容,影响对设计模式的理解,这里使用了同名的类和方法,尽可能减少外部的依赖。
模拟Spring的HandlerInterceptor
1 2 3 4 5 6 7 8
| package com.bestrookie.design;
public interface HandlerInterceptor { boolean preHandle(String request,String response,Object handler); }
|
模拟单点登录功能
1 2 3 4 5 6 7 8 9 10 11
| package com.bestrookie.design;
public class SsoInterceptor implements HandlerInterceptor{ public boolean preHandle(String request, String response, Object handler) { String ticket = request.substring(1, 8); return "success".equals(ticket); } }
|
违背设计模式的实现
继承的方式是一种比较通用的方式,通过继承后重写方法,并将逻辑覆盖进去。对于一些简单的且不需要持续维护和扩展的场景,此种方式的实现并不会有什么问题,也不会导致子类过多。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.bestrookie.design; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
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) { String ticket = request.substring(1, 8); boolean success = "success".equals(ticket); if(!success){ return false; } String userId = request.substring(8); String method = authMap.get(userId); return "queryUserInfo".equals(method); } }
|
这部分代码的实现是通过继承类后重写方法,将个人访问方法的功能添加到方法中。这段代码比较清晰,如果面对比较复杂的业务流程,代码就会变得混乱。
装饰器模式重构代码
装饰器主要解决的是直接集成时因功能不断横向扩展导致子类膨胀的问题,而使用装饰器模式比直接集成更加灵活,同时也不再需要维护子类。
在装饰器模式中,有四点比较重要:
- 抽象构件角色:定义构件抽象接口
- 具体构件角色:实现抽象接口,可以是一组;
- 装饰角色:定义抽象类并继承接口中的方法,保证一致性;
- 具体装饰角色:扩展装饰具体的实现逻辑
通过以上四种实现装饰模式,主要核心内容会体现在抽象类的定义和实现方面
以上是装饰器模式实现的类图结构,重点的类是SsoDecorator,它表示一个抽象类主要完成了对接HandlerInterceptor的继承。当装饰角色继承接口后,会提供构造函数SsoDecorator,入参是继承的接口实现类,可以很方便地扩展出不同的功能组件。
抽象类装饰角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.bestrookie.design.decorator; import com.bestrookie.design.HandlerInterceptor;
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); } }
|
在装饰类中,有三点需要注意:继承了处理接口,提供了构造函数,覆盖了方法preHandle。以上是装饰器模式的核心处理部分,可以替换对子类集成的方式,实现逻辑功能的扩展。
装饰角色逻辑实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.bestrookie.design.decorator; import com.bestrookie.design.HandlerInterceptor; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
public class LoginSsoDecorator extends SsoDecorator{ public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) { super(handlerInterceptor); } 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) { boolean success = super.preHandle(request, response, handler); if (!success){ return false; } String userId = request.substring(8); String method = authMap.get(userId); return "queryUserInfo".equals(method); } }
|
在具体实现类中,继承了装饰类SsoDecrator,现在可以扩展方法preHandle的功能。在具体的而实现代码中可以看到,这里只关心扩展部分的功能,同时不会影响原有类的核心服务,也不会因为使用继承方式而导致出现多余子类,增加了整体的灵活性。
总结
装饰器模式满足单一职责原则,可以在自己的装饰类中完成功能逻辑的扩展而不影响主类,同时可以按需在运行时添加和删除这部分逻辑。另外,装饰器模式和继承父类重写方法在某些时候要按需选择,并非某个方式就是最好的。装饰器模式实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口可以通过构造函数传递其实现类,由此增加扩展性,并重写方法中可以通过父类实现的功能。