# 定义
装饰器模式就像俄罗斯套娃,它的核心是在不改变原有类的基础上给类新增功能。对于不改变原有类,可能有的人会想到继承、AOP 切面,虽然这些方式都可以实现,但是使用装饰器模式是另外一种更灵活的思路,能够避免继承导致的子类过多问题,也可以避免AOP带来的复杂性问题。
**装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。**
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景:
1. 扩展一个类的功能。
2. 动态增加功能,动态撤销。
>注意事项:可代替继承。
# 实践
这里模拟一个单点登录权限功能扩充的场景。
```
public interface HandlerInterceptor {
boolean preHandle(String request, String response, Object handler);
}
```
这里模拟的是Spring类HandlerInterceptor,实现接口功能SsoInterceptor模拟的单点登录拦截服务。为了避免引入太多的 Spring 内容,影响对设计模式的理解,这里使用了同名的类和方法,尽可能减少外部的依赖。
模拟单点登录功能
```
public class SsoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(String request, String response, Object handler) {
// 模拟获取cookie
String ticket = request.substring(1, 8);
// 模拟校验
return ticket.equals("success");
}
}
```
## 使用装饰器模式实现
在装饰类中,有三点需要注意:继承了处理接口,提供了构造函数,覆盖了方法preHandle。以上三点是装饰器模式的核心处理部分,可以替换对子类继承的方式,实现逻辑功能的扩展。
```
public abstract class SsoDecorator implements HandlerInterceptor {
private HandlerInterceptor handlerInterceptor;
private SsoDecorator(){}
public SsoDecorator(HandlerInterceptor handlerInterceptor) {
this.handlerInterceptor = handlerInterceptor;
}
@Override
public boolean preHandle(String request, String response, Object handler) {
return handlerInterceptor.preHandle(request, response, handler);
}
}
```

以上是装饰器模式实现的类图结构,重点的类是 SsoDecorator,它表示一个抽象类主要完成了对接口 HandlerInterceptor 的继承。当装饰角色继承接口后,会提供构造函数 SsoDecorator(HandlerInterceptor handlerInterceptor),入参是继承的接口实现类,可以很方便地扩展出不同的功能组件。
```
@Slf4j
public class LoginSsoDecorator extends SsoDecorator {
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);
log.info("模拟单点登录方法访问拦截校验:{} {}", userId, method);
// 模拟方法校验
return "queryUserInfo".equals(method);
}
}
```
在具体的装饰类实现中,继承了装饰类 SsoDecorator,现在可以扩展方法preHandle的功能。在具体的实现代码中可以看到,这里只关心扩展部分的功能,同时不会影响原有类的核心服务,也不会因为使用继承方式而导致出现多余子类,增加了整体的灵活性。
单元测试
```
public class ApiTest {
@Test
public void test_sso() {
SsoInterceptor ssoInterceptor = new SsoInterceptor();
String request = "1successhuahua";
boolean success = ssoInterceptor.preHandle(request, "ewcdqwt40liuiu", "t");
System.out.println("登录校验:" + request +(success ? " 放行" : " 拦截"));
}
@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 ? " 放行" : " 拦截"));
}
}
```
# 总结
装饰器主要解决的是直接继承时因功能的不断横向扩展导致子类膨胀的问题,而使用装饰器模式比直接继承更加灵活,同时也不再需要维护子类。
在装饰器模式中,有四点比较重要:
- 抽象构件角色(Component):定义抽象接口;
- 具体构件角色(ConcreteComponent):实现抽象接口,可以是一组;
- 装饰角色(Decorator):定义抽象类并继承接口中的方法,保证一致性;
- 具体装饰角色(ConcreteDecorator):扩展装饰具体的实现逻辑。
通过以上四种实现装饰器模式,主要核心内容会体现在抽象类的定义和实现方面。
装饰器模式满足单一职责原则,可以在自己的装饰类中完成功能逻辑的扩展而不影响主类,同时可以按需在运行时添加和删除这部分逻辑。
另外,装饰器模式和继承父类重写方法在某些时候要按需选择,并非某个方式就是最好的。装饰器模式实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口可以通过构造函数传递其实现类,由此增加扩展性,并重写方法中可以通过父类实现的功能。
装饰器模式就像夏天热时穿短裤,冬天冷时穿棉裤,下雨时穿雨衣一样,我们本身并没有被改变,而外形却用不同的服饰表现。生活中的场景比比皆是,如果能够将生活中的例子融入代码实现中,往往会创造出更加优雅的实现方式。

【设计模式】装饰器模式