1. 定义
装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
装饰模式可以从现实世界中找到影子。
比如 PS,对图像有锐化、高斯模糊、明暗对比、饱和度调整等功能,这些都视为对图像的装饰,然后这些功能可以进行组合也可以单独使用,应用在各种不同的图像上面,可以灵活丰富原有图像的展示。
比如化妆,需要不同的化妆品组合搭配,不同搭配有不同的效果,动态选择化妆品,对脸进行装饰。
比如学习的过程,一个学习的过程就是对个人知识库的装饰过程。
永远的希斯莱杰2. 设计
主要角色:
- 抽象构件,定义构件的基本方法。
- 具体构件,实现抽象构件的基本方法。装饰器会去装饰。
- 抽象装饰类,继承抽象构件的基本方法,来实现和具体构件一样的行为。维护抽象构件的引用。
- 具体装饰类,实现抽象装饰类的基本方法,会对构建增加新职责,增加新方法。
类图如下:
装饰模式-类图抽象构件:
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();
}
}
上面例子展示了装饰模式的基本结构以及使用的方式。
实际应用场景一般不会有这么理想化的模型。
对于装饰者新增的操作方法的使用方式,会有两种模型:
- 透明(Transparent)装饰模式。
- 半透明(Semi-transparent)装饰模式。
2.1. 透明装饰者
使用如下:
- 客户端面向抽象编程。
- 构件,声明的是抽象构建者。
- 修饰者,声明的还是抽象构建者。
比如上面的那个简单例子。
// 构件
IComponent component = new ConcreteComponent();
component.sayHello();
// 使用 A 修饰
component = new DecoratorA(new ConcreteComponent());
component.sayHello();
这样的话修饰者和构件对于客户端就完全没有区别了,不用关心修饰者到底增加了什么方法,也不需要调用到修饰者增加的方法。
这里的修饰者还可以继续当成构件,抛出到下一个修饰者修饰。
对于客户端来说,透明修饰者模式,修饰者还是构件。
2.2. 半透明装饰者
使用如下:
- 客户端需要显示调用装饰者新增的方法。
- 构件,声明的是抽象构件者。
- 修饰者,声明的是具体修饰者。没有抽象修饰者。
所以具体的构件者对客户端透明,但修饰者没有。
修饰者新增的方法是独立的,客户端可以调用该方法来完成对构件的修饰。
// 构件
IComponent component = new ConcreteComponent();
component.sayHello();
// 使用 A 修饰
DecoratorA decoratorA = new DecoratorA(componet);
decoratorA.operationA();
这种方式设计简单,不需要对装饰者和构件进行接口统一,所以修饰者和构件可以不在同一个继承体系下面。
缺点就是无法反复进行修饰。
对于客户端来说,半透明修饰者模式,修饰者可以不是构件。
3. 应用
使用者面像抽象构建编程,无需关心中途被修饰成什么样,修饰过程对使用者透明。
可以在不增加子类的情况下,不使用继承的方式,基于组合的方式,对一个对象的功能进行扩展。
除了对一个构件进行修饰,还可以把修饰完的构件再加入修饰器中反复修饰。
应用场景:
- 动态给对象增加职责和功能。
-
对继承的优化。
- 在一些无法使用继承的类,比如声明为 final 类型的类需要扩展功能的。
- 在已经存在大量扩展类的情况下,没增加一个扩展需要创建多个子类。
3.1. Jave IO
Java IO 框架是很经典的修饰者模式。
输入字节流 InputStream。
- 抽像构件类为 InputStream。
- 抽像修饰者为 FilterInputStream,维持被修饰的 InputStream 的引用。
- 具体构件类有 FileInputStream、ByteArrayInputStream 等。
- 具体修饰者有 BufferedInputStream、DataInputStream 等。
输出字节流 OutputStream。
- 抽像构件类为 OutputStream。
- 抽像修饰者为 FilterOutputStream,维持对被修饰的 OutputStream 的引用。
- 具体构件类有 FileOutputStream、ByteArrayOutputStream 等。
- 具体修饰者有 BufferedOutputStream、DataOutputStream 等。
可以发现这里使用的是修饰中模式中的透明修饰者,可以对字节流进行反复修饰。
3.2. Dubbo 对扩展点的修饰
Dubbo 在生成 Protocol 的 Invoker 前,会对 Protocol 进行修饰,增加日志、权限、异常处理等等。
对 Protocol 修饰使用的是 ProtocolFilterWrapper。对应修饰者模式中的修饰者。
抽象构件 Protocol 定义如下:
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
抽象修饰者 ProtocolFilterWrapper 实现了 Protocol,并且持有 Protocol 的引用,然后 export
和 refer
的方法中增强。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
...
public <T> Exporter<T> export(Invoker<T> 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 <T> Invoker<T> refer(Class<T> 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 <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> 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<T> next = last;
last = new Invoker<T>() {
public Class<T> 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 的时候,这一系列过滤器就会被执行。
这个扩展点修饰如下:
- 使用修饰者增强了 Protocol 的 export 和 refer 方法。
- export 和 refer 在创建 Invoker 的地方,使用责任链模式组装 Filter 链。在 Invoker 调用 invoke 的使用过滤器操作。
4. 特点
装饰模式的几个要点:
- 动态扩展。
- 不改变原来的类。
- 基于组合而不是继承。
装饰者模式可以用来代替继承。不断装饰的过程就类似于继承对父类的扩展和重写。
4.1. 优点
- 单一职责。每个装饰者关注某一个具体的功能。
- 功能复用。而且面向抽象构件编程,可以在多个具体构建者中应用同一种功能。比如 BufferInputStream 可以对不同的字节流增加缓存的能力。
- 解耦。具体构件者和具体装饰者相互独立。
- 灵活。动态扩展,避免静态继承导致子类膨胀。
- 易扩展。增加新的构件者和具体修饰者,无需修改之前的代码,符合开闭原则。
4.2. 缺点
- 小对象多。因为不同的功能会建立单独一个修饰者实现,如果要组合一个对象可能会同时创建多个修饰者。
- 排错复杂。动态组合功能,出问题需要排查每一个功能类。
网友评论