定义:装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
定义与类图来自百度百科。这个定义说到了装饰器模式的几个重点,不改变源文件,不使用继承,然后可以扩展一个对象的功能,这几个重点很好的介绍了装饰器模式。那么装饰器模式是怎么实现的呢,类图上Componet是一个被装饰的接口,ConcreteComponent是具体的被装饰的类,通过Decorator实现被装饰的接口,并持有一个被装饰接口的引用(组合的方式),通过调用持有的引用来实现被装饰的原方法,然后在具体装饰类ConcreteDecoratorA和B里去扩展新的方法,或是在原方法里面进行包装都是可以的。
IO的整体架构正是用到了装饰器模式,我在学习这个模式之后,再去看IO中类与类之间的关系,一下子就恍然大悟了,瞬间明白操作IO的时候为什么要封装来封装去。关于IO在最后会提到,这里先开始写一个装饰器模式。这里我举一个漫威的例子。
首先是被装饰的接口(人类),一个人类都有可能会有如下的几个行为。
package test;
public interface Human {
void listen();
void walk();
void eat();
void fly();
}
然后是被装饰的类(普通人类),实现接口。
package test;
public class NormalHuman implements Human {
@Override
public void listen() {
System.out.println("人类普通的听觉");
}
@Override
public void walk() {
System.out.println("人类正常走路");
}
@Override
public void eat() {
System.out.println("正常吃饭");
}
@Override
public void fly() {
System.out.println("普通人不能飞");
}
}
然后就是类图中的Decorator,算是一个中间类,(普通人进化成超能力者的中介)
package test;
public class Decorator implements Human {
Human human;
public Decorator(Human human) {
this.human = human;
}
@Override
public void listen() {
human.listen();
}
@Override
public void walk() {
human.walk();
}
@Override
public void eat() {
human.eat();
}
@Override
public void fly() {
human.fly();
}
}
然后是具体装饰的类(钢铁侠和蜘蛛侠)
package test;
public class ironMan extends Decorator {
public ironMan(Human human) {
super(human);
}
// 钢铁侠新增能力,飞行
@Override
public void fly() {
System.out.println("钢铁侠的飞行");
}
@Override
// 钢铁侠改良了正常人的跑步
public void walk() {
System.out.println("跑的飞快");
}
}
package test;
public class spiderMan extends Decorator {
public spiderMan(Human human) {
super(human);
}
// 蜘蛛侠新增能力,吐丝
public void Spinning() {
System.out.println("蜘蛛侠吐丝");
}
@Override
// 蜘蛛侠可以飞檐走壁,但走路还是和正常人一样走
public void walk() {
System.out.println("蜘蛛侠的飞檐走壁");
super.walk();
}
@Override
// 蜘蛛侠改进普通人的听觉
public void listen() {
System.out.println("蜘蛛侠敏锐的听觉");
}
}
我们来试试普通人进化成超能力者的过程吧!
package test;
public class testA {
public static void main(String[] args) {
Human man = new NormalHuman();
System.out.println("正常人:");
man.walk();
man.listen();
man.eat();
System.out.println("装饰成钢铁侠了:");
ironMan Tony = new ironMan(man);
Tony.fly();
Tony.walk();
Tony.listen(); // 但是钢铁侠也是人,听觉还是普通人的听觉(还是拥有被装饰类的方法)
System.out.println("装饰成蜘蛛侠了:");
spiderMan Parker = new spiderMan(man);
Parker.Spinning();
Parker.listen();
Parker.walk();// 蜘蛛侠可以飞檐走壁,但走路还是普通人一样的走
Parker.eat(); // 但是蜘蛛侠也是人,吃饭还是跟普通人一样(还是拥有被装饰类的方法)
Parker.fly(); // 蜘蛛侠并没有飞行的功能,所以他和普通人一样不能飞行
System.out.println("装饰成钢铁侠后再装饰成蜘蛛侠:");
Parker = new spiderMan(new ironMan(man)); // 但在装饰成钢铁侠后再装饰成蜘蛛侠,蜘蛛侠也能飞
Parker.fly();
Parker.walk(); // 结合了钢铁侠和蜘蛛侠,现在蜘蛛侠除了飞檐走壁,跑的也飞快了!
}
}
控制台打印
由上面的结果我们可以得出以下结论:
1. 可以扩展原先类没有的方法,比如钢铁侠有了普通人没有的飞行
2. 可以修改原先类的方法,比如钢铁侠走路跟普通人不一样,他跑的飞快
3. 装饰之后的类也还是可以拥有被装饰原先的方法,比如钢铁侠也是普通人,吃饭和普通一样
4. 多重装饰之后,本来蜘蛛侠不能飞,但装饰成钢铁侠,再装饰成的蜘蛛侠就可以拥有钢铁侠的方法(飞行),本来只有飞檐走壁和普通走路,多重装饰之后走路都飞快了!
相信这个例子应该可以让大家很好理解装饰器模式了,写这个类我也花了很久去思考,在这个思考的过程,我也更加深入的理解这个设计模式,所以我建议大家也可以动手写一写,把它写出来是促进你思考的过程。
开篇有提到IO也是用了装饰器模式,这里就粗略的讲一讲吧。先来介绍一下IO结构
InputStream (类似Human接口)截取部分。
package java.io;
public abstract class InputStream implements Closeable {
// MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
// use when skipping.
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//read方法,这里InputStream其实也用到了模板方法模式,具体的read()推迟到了子类去实现,而这里只需要提供大致骨架
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
}
然后就是具体的实现,FileInputStream(类似普通人类),实现了InputStream的方法,这里就不贴了
然后就是FilterInputStream (类似Decorator中间类),继承了InputStream,持有一个InputStream引用,实现所有InputStream的方法。与我举的例子一摸一样,这里也不贴了。
然后就是BufferedInputStream (类似钢铁侠蜘蛛侠),装饰的类,他继承了上面所说的中间类,并修改了read方法,并新增了一个reset方法(在InputStream中抛异常,没有功能)。
然后还是装饰的类DataInputStream,它扩展了原先的字节类,新增功能读Int,读Boolean。
接下来看具体实现吧
package test;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class testA {
@SuppressWarnings({ "resource", "unused" })
public static void main(String[] args) throws IOException {
File file = new File("d:/test.txt");
// 先写入一个值,后面数据流用到
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(fos);
dos.writeInt(123);
FileInputStream fis = new FileInputStream(file); // 这里是普通人类
int length = fis.available(); // 保存流长度
System.out.println("普通流没有mark功能" + fis.markSupported());
System.out.println("-----------------装饰成了缓存流----------------------");
BufferedInputStream bis = new BufferedInputStream(fis); // 这里装饰成了缓存流
System.out.println("缓存流有mark功能" + bis.markSupported());
System.out.println("-----------------装饰成数据流----------------------");
DataInputStream dis = new DataInputStream(fis);
System.out.println("数据流还是没有mark功能" + dis.markSupported());
System.out.println("-----------------装饰成缓存流后再装饰成数据流----------------------");
dis = new DataInputStream(bis); // 这样,数据流又有读Int之类的方法又有mark,reset的方法
System.out.println("数据流有mark功能了" + dis.markSupported());
System.out.println(dis.readInt());
}
}
控制台打印
这里的结果其实和我举的例子差不多,通过学习装饰器模式,应该可以对IO有了一个大致的理解了。
注:由于我的水平有限,有些地方说的可能有问题?欢迎大家指出,互相讨论互相学习进步!