装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
装饰模式可以从现实世界中找到影子。
比如 PS,对图像有锐化、高斯模糊、明暗对比、饱和度调整等功能,这些都视为对图像的装饰,然后这些功能可以进行组合也可以单独使用,应用在各种不同的图像上面,可以灵活丰富原有图像的展示。
比如化妆,需要不同的化妆品组合搭配,不同搭配有不同的效果,动态选择化妆品,对脸进行装饰。
比如学习的过程,一个学习的过程就是对个人知识库的装饰过程。
主要角色:
类图如下:
抽象构件:
public interface IComponent {
void sayHello();
}
具体构件,用来被修饰的对象:
public class ConcreteComponent implements IComponent {
public void sayHello() {
System.out.println("hello.");
}
}
抽象修饰者,也继承抽象构件,可以视为特殊的构件类:
public class DecoratorA extends Decorator {
public DecoratorA(IComponent component) {
super(component);
}
@Override
public void sayHello() {
super.sayHello();
System.out.println("A");
}
}
使用者组合调用,对具体构建进行一次修饰,或者多次修饰:
public class TestDecorator {
public static void main(String[] args) {
// 原始构件
IComponent component = new ConcreteComponent();
component.sayHello();
// 使用 A 修饰
System.out.println();
component = new DecoratorA(new ConcreteComponent());
component.sayHello();
// 使用 B 修饰
System.out.println();
component = new DecoratorB(new ConcreteComponent());
component.sayHello();
// 使用 A 修饰然后又使用 B 修饰
System.out.println();
component = new DecoratorB(new DecoratorA(new ConcreteComponent()));
component.sayHello();
}
}
上面例子展示了装饰模式的基本结构以及使用的方式。
实际应用场景一般不会有这么理想化的模型。
对于装饰者新增的操作方法的使用方式,会有两种模型:
使用如下:
比如上面的那个简单例子。
// 构件
IComponent component = new ConcreteComponent();
component.sayHello();
// 使用 A 修饰
component = new DecoratorA(new ConcreteComponent());
component.sayHello();
这样的话修饰者和构件对于客户端就完全没有区别了,不用关心修饰者到底增加了什么方法,也不需要调用到修饰者增加的方法。
这里的修饰者还可以继续当成构件,抛出到下一个修饰者修饰。
对于客户端来说,透明修饰者模式,修饰者还是构件。
使用如下:
所以具体的构件者对客户端透明,但修饰者没有。
修饰者新增的方法是独立的,客户端可以调用该方法来完成对构件的修饰。
// 构件
IComponent component = new ConcreteComponent();
component.sayHello();
// 使用 A 修饰
DecoratorA decoratorA = new DecoratorA(componet);
decoratorA.operationA();
这种方式设计简单,不需要对装饰者和构件进行接口统一,所以修饰者和构件可以不在同一个继承体系下面。
缺点就是无法反复进行修饰。
对于客户端来说,半透明修饰者模式,修饰者可以不是构件。
使用者面像抽象构建编程,无需关心中途被修饰成什么样,修饰过程对使用者透明。
可以在不增加子类的情况下,不使用继承的方式,基于组合的方式,对一个对象的功能进行扩展。
除了对一个构件进行修饰,还可以把修饰完的构件再加入修饰器中反复修饰。
应用场景:
Java IO 框架是很经典的修饰者模式。
输入字节流 InputStream。
输出字节流 OutputStream。
可以发现这里使用的是修饰中模式中的透明修饰者,可以对字节流进行反复修饰。
Dubbo 在生成 Protocol 的 Invoker 前,会对 Protocol 进行修饰,增加日志、权限、异常处理等等。
对 Protocol 修饰使用的是 ProtocolFilterWrapper。对应修饰者模式中的修饰者。
抽象构件 Protocol 定义如下:
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
void destroy();
}
抽象修饰者 ProtocolFilterWrapper 实现了 Protocol,并且持有 Protocol 的引用,然后 export
和 refer
的方法中增强。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
...
public Exporter export(Invoker invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
public Invoker refer(Class type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
...
}
修饰者 ProtocolFilterWrapper 在修饰 Protocol 的过程中,还应用了责任链模式。
private static Invoker buildInvokerChain(final Invoker invoker, String key, String group) {
Invoker last = invoker;
List filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (filters.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i --) {
final Filter filter = filters.get(i);
final Invoker next = last;
last = new Invoker() {
public Class getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
使用 SPI 工具 ExtensionLoader 从配置文件 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
把过滤器列表全部加载出来。
文件如下:
cache=com.alibaba.dubbo.cache.filter.CacheFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter
然后使用责任链,把所有的 Filter 组装成链表。然后返回链表头部的 Invoker。
这样 Invoker 触发 invoke 的时候,这一系列过滤器就会被执行。
这个扩展点修饰如下:
装饰模式的几个要点:
装饰者模式可以用来代替继承。不断装饰的过程就类似于继承对父类的扩展和重写。