装饰器(Decorator)
简述:
动态的给一个对象添加一些额外的职责,就增加功能来说,相比生成子类更为灵活。
装饰器(Decorator)模式,是一种在运行期动态给某个对象的实例增加功能的方法。
在IO的Filter模式一节中其实已经讲过装饰器模式了。在Java标准库中。InputStream时抽象类。FileInputStream、ServletInputStream、Socket.getInputStream()这些InputStream都是最终数据源。
现在,要给不同的最终数据源增加缓冲功能,计算签名功能,加密解密功能,那么,3个最终数据源,3种功能一共需要9个子类。如果继续增加最终数据源,或者增加新功能,子类会爆炸式的增长,这种设计方式显然是不可取的。
Docorator模式的目的就是把一个一个的附加功能,用Docotor的方式给一层一层的加到原始的数据源上,最终,通过组合获得我们想要的功能。
例如:给FileInputStream增加缓冲和解压压缩功能,用Docorator模式写醋回来如下:
InputStream fis =new FileInputStream("test.gz");//创建原始数据源
InputStream bis=new BufferedInputStream(fis);//增加缓冲功能
InputSream gis=new GZIPInpitStream(bis);//增加解压缩功能
或者一次性写为这样:
InputStream input=new GZIPInputStream( new BufferedInputStream( new FileInputStream("test.gz") ) ) ;
观察BufferedInputStream和GZIPInputStream。它们实际上都是从FilterInputStream继承的,这个FilterInputStream就是一个抽象的Decorator。我们用图把Decorator模式画出来如下:

最顶层的Component是接口,对应到IO的就是InputStream这个抽象类。ComponentA,ComponentB是实际的子类,对应到IO的就是FileInputStream、ServletInputStream这些数据源。Decorator是用于实现各个符驾功能的抽象装饰器,对应到IO的就是FilterInputStream。而Docorator派生的就是一个一个的装饰器,它们每个都有独立的功能,对应到IO的就是BufferedInputStream,GZIPInputStream等。
Decorator模式有什么好处呢?它实际上把核心功能和附加功能给分开了。核心功能指FileInputStream这些真正读数据的源头,附加功能指加缓冲、压缩、解密这些功能。如果我们要新增核心功能,就增加Component的子类,例如ByteInputStream。如果我们要增加附加功能,就增加Decorator的子类,例如CipherInputStream。两部分都可以独立地扩展,而具体如何附加功能,由调用方自由组合,从而极大地增强了灵活性。
如果我们要自己设计完整的Decorator模式,应该如何设计?
假如现在我们要设置一个能够输出Html标签的文本内容,首先我们写一个顶层Component:
package 装饰者模式.HTML文本处理样例; /** * @author :wangjiazhi * @creatDate:2021年9月2日10点17分 * @description:这是一个用于写出HTML文本的接口 */ public interface TextNode { /** * * @param text 要处理的基本内容 */ void setText(String text); /** * @return 返回处理好的文本 */ String getText(); }
这个接口定义了基本的文本设置 文本获取。对于核心节点 ,例如<span>标签需要写一个实现类:
package 装饰者模式.HTML文本处理样例; public class SpanNode implements TextNode{ String text; @Override public void setText(String text) { this.text=text; } @Override public String getText() { return "<span>"+text+"</span>"; } }
可以看到,对于文本进行设置以后,文本就可以达到具有标签的效果了,然而对于一些非核心的标签,但是经常需要用到,实现便于灵活使用的效果,我们要先定义一个装饰者抽象类NodeDecorator:
package 装饰者模式.HTML文本处理样例; /** * @author :wangjiazhi * @description:这是一个用于增强自身功能的装饰者类持有一个接口对象 */ public abstract class NodeDecorator implements TextNode { //抽象类继承了接口 不必去实现接口中的所有方法 可以实现一部分 剩下的可以交给子类去实现 TextNode textNode; public NodeDecorator(TextNode textNode){ this.textNode=textNode; } @Override public void setText(String text) { this.textNode.setText(text); } }
可以看到它持有一个TextNode对象,有点像代理模式。对于装饰者模式与代理模式,继承之间的联系,差异,我会在我的下一篇随笔中写到,本次只是简单的使用,只了解这种思想。
非核心的加粗标签装饰器如下:
package 装饰者模式.HTML文本处理样例; /** * @author :wanhjiazhi * @creatDate:2021年9月2日10点17分 * @description:这是一个用于加粗的装饰者类 */ public class BoldDecorator extends NodeDecorator { public BoldDecorator(TextNode textNode){ super(textNode); } @Override public String getText() { return "<b>"+textNode.getText()+"</b>"; } }
非核心的斜体标签装饰器如下:
package 装饰者模式.HTML文本处理样例; public class ItalicDecorator extends NodeDecorator{ public ItalicDecorator(TextNode textNode){ super(textNode); } @Override public String getText() { return "<i>"+textNode.getText()+"</i>"; } }
我们进行代码的演示:
package 装饰者模式.HTML文本处理样例; public class Main { public static void main(String[] args) { TextNode textNodeSpan=new SpanNode(); TextNode textNodeBoldSpan=new BoldDecorator(new SpanNode()); TextNode textNodeItalicBoldSpan=new ItalicDecorator(new BoldDecorator(new SpanNode())); textNodeSpan.setText("hello world"); textNodeBoldSpan.setText("hello world"); textNodeItalicBoldSpan.setText("hello world"); System.out.println(textNodeSpan.getText());//<span>hello world</span> System.out.println(textNodeBoldSpan.getText());//<b><span>hello world</span></b> System.out.println(textNodeItalicBoldSpan.getText());//<i><b><span>hello world</span></b></i> } }
对于这种模式的理解:可以看出来,可以灵活的层层嵌套达到各种标签的搭配,相比单纯继承实现更加灵活。
结构图如下:

结语:
使用Decorator模式,可以独立增加核心功能,也可以独立增加附加功能,二者互不影响;
可以在运行期动态地给核心功能增加任意个附加功能。

浙公网安备 33010602011771号