一、定义
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
在类图中,各个角色的说明如下:Component,抽象构件:Component是一个接口或者抽象类,是定义我们最核心的对象,也可以说是最原始的对象
ConcreteComponent,具体构件,或者基础构件:ConcreteComponent是最核心、最原始、最基本的接口或抽象类Component的实现,可以单独用,也可将其进行装饰
Decorator,装饰角色:一般是一个抽象类,继承自或实现Component,在它的属性里面有一个变量指向Component抽象构件,我觉得这是装饰器最关键的地方。
ConcreteDecorator,具体装饰角色:ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,它们可以把基础构件装饰成新的东西。
二、装饰器模式的应用场景
装饰器模式其实在生活和生产中的案例特别多,例如IO文件流就是一个很直接简单的装饰器模式,在生活中也有,例如给窗户挂窗帘,可以给窗帘上加个小挂件装饰下,装饰完后觉得一个挂件不够再装一个。山东的姐夫让我想起了吃货的一个场景。山东人吃煎饼,喜欢在里面包东西,每个人想在里面包的菜都不一样,这里面就可以以大饼和前面说的窗帘为主体,挂件菜啥的就是装饰。//抽象构件 public abstract class Component { // 抽象的方法 public abstract void cost(); }
//具体基础构件 public class ConcreteComponent extends Component{ public void cost(){ System.out.println("这是具体基础构件"); } }
//抽象装饰角色 public abstract class Decorator extends Component{ private Component component = null; public Decorator(Component component){ this.component = component; } @Override public void cost(){ this.component.cost(); } }
//具体装饰角色 public class ConcreteDecorator extends Decorator{ public ConcreteDecorator(Component component){ super(component); } // 定义自己的修饰逻辑 private void decorateMethod(){ System.out.println("定义自己的修饰逻辑"); } // 重写父类的方法 public void cost(){ this.decorateMethod(); super.cost(); } }
public class DecoratorDemo { public static void main(String[] args){ Component component = new ConcreteComponent(); // 第一次修饰,比如,加一个花边 component = new ConcreteDecorator(component); // 第二次修饰,比如,加一个图片 component = new ConcreteDecorator(component); // 修饰后运行,将所有加一起 component.cost(); } }
三、 装饰器模式在Java I/O系统中的实现
前面说过IO用的就是装饰器
InputStream作为抽象构件,其下面大约有如下几种具体基础构件,从不同的数据源产生输入:
- ByteArrayInputStream,从字节数组产生输入;
- FileInputStream,从文件产生输入;
- StringBufferInputStream,从String对象产生输入;
- PipedInputStream,从管道产生输入;
- SequenceInputStream,可将其他流收集合并到一个流内;
FilterInputStream作为装饰器在JDK中是一个普通类,其下面有多个具体装饰器比如BufferedInputStream、DataInputStream等。我们以BufferedInputStream为例,使用它就是避免每次读取时都进行实际的写操作,起着缓冲作用。我们可以在这里稍微深入一下,站在源码的角度看下。
FilterInputStream内部封装了基础构件:
protected volatile InputStream in;
而BufferedInputStream在调用其read()读取数据时会委托基础构件来进行更底层的操作,而它自己所起的装饰作用就是缓冲,在源码中可以很清楚的看到这一切:
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; } private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buffer.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else if (buffer.length >= MAX_BUFFER_SIZE) { throw new OutOfMemoryError("Required array size too large"); } else { /* grow buffer */ int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; // 看这行就行了,委托基础构件来进行更底层的操作 int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; } private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; }
这部分的代码很多,这里没有必要考虑这段代码的具体逻辑,只需要看到在BufferedInputStream的read方法中通过getInIfOpen()获取基础构件从而委托其进行更底层的操作(在这里是读取单个字节)就可以说明本文所要说的一切了。至于I/O类库中的其他设计诸如OutputStream、Writer、Reader,是一致的
四、总结
装饰者模式的优缺点
优点:
1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3、装饰者完全遵守开闭原则。
缺点:
1、会出现更多的代码,更多的类,增加程序复杂性。
2、动态装饰时,多层装饰时会更复杂。
补充:代理模式和装饰器模式看起来很像,但是这两种设计模式所面向的功能扩展不一样,装饰器模式强调自身功能的扩展,Decorator所做的就是增强ConcreteComponent的功能,主体对象为ConcreteComponent,着重功能的变化;代理模式强调对代理过程过程的控制,Proxy完全掌握对RealSubject的访问控制,因此Proxy可以决定对RealSubject进行功能的扩展