IO流与装饰者

装饰者模式的定义:动态的将责任附加到对象身上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

了解装饰者模式

装饰者模式的定义:动态的将责任附加到对象身上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

在定义在提到了几个关键点:

  1. 动态附加
  2. 扩展功能
  3. 比继承更优

由此让我们深入理解这个神奇的模式!

深入理解装饰者模式

从名称或许可以看出一些端倪,该模式主要的功能就是装饰,换句话说,就是要为原对象穿几件不同的衣服,但是并不会影响原对象本身的功能,所以叫做扩展功能,而不是改变功能。

使用继承进行面向对象编程或许是个好主意,但是通常会因为子类覆盖了父类的方法而导致功能的改变,有时我们并不需要这种改变。

比如在学习的过程中我们需要买一些书籍,那首先需要为书本创建一个共同的类Book,这不是什么难事,过程非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public abstract class Book {
protected String description;
/**
* 获得书本的描述信息
* @return
*/
public String getDescription() {
return description;
}
/**
* 抽象方法,子类必须实现
* @return 书本的价格
*/
public abstract int price();
}

目前正在学习设计模式,决定买一本《Head First设计模式》,那么需要创建这本书的类,继承自Book,并且在构造方法中改变描述信息,这样通过getDescription()获取这本书的有关信息,假设这本书只卖人民币50元

1
2
3
4
5
6
7
8
9
10
11
12

public class HeadFirstDesignPattern extends Book {

public HeadFirstDesignPattern() {
description="《Head First 设计模式》";
}

@Override
public int price() {
return 50;
}
}

到此为止,可以买书了,但是为了保护书本和提升看书体验,可能应该要附加一些其他的东西,比如需要一个漂亮的包装和精美的书签。但是并不是直接创建这两个类这么简单,既然是装饰者模式,需要一个装饰者,而具体的装饰可以有很多,所以包装和书签如果能继承自同一个类,那是很棒的!
而且很明显的知道对于包装和书签来说也需要返回价格和设置描述信息,没有必要再重写一遍,所以首先应该创建一个装饰者抽象类,继承自Book,这样能保证所有的装饰者都可以重写cost方法用来返回价格

1
2
3
4

public abstract class BookDecorator extends Book {
public abstract String getDescription();
}

现在可以感叹一下,装饰者也太简单了吧!事实上确实如此,我们主要的工作再具体的装饰者身上,那么现在可以实现包装和书签了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

/*包装类*/
public class PackingBox extends BookDecorator{
Book book;
public PackingBox(Book book) {
super();
this.book=book;
}
@Override
public String getDescription() {
return book.getDescription()+",[包装盒]";
}
@Override
public int price() {
return 10+book.price();
}
}

/*书签类*/
public class Bookmark extends BookDecorator {
Book book;
public Bookmark(Book book) {
super();
this.book=book;
}
@Override
public String getDescription() {
return book.getDescription()+",[书签]";
}
@Override
public int price() {
return 5+book.price();
}
}

或许还没有搞明白到底怎么回事,不过没有关系,先来买一本试试,测试的结果显示再注释中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class Test {
public static void main(String[] args) {
Book headfirst=new HeadFirstDesignPattern();
System.out.println("不加任何装饰的东西:");
System.out.println(headfirst.getDescription()+" ¥"+ headfirst.price());

System.out.println("增加包装盒和书签");
headfirst=new PackingBox(new Bookmark(headfirst));
System.out.println(headfirst.getDescription()+" ¥"+ headfirst.price());
}
/*
不加任何装饰:
《Head First 设计模式》 ¥50
装饰包装盒和书签
《Head First 设计模式》,[书签],[包装盒] ¥65
*/
}

在看到运行结果之后,让我们对装饰者模式进行更加深入的了解:

  1. 在装饰者模式种存在被装饰对象和装饰者,它们拥有相同的父类(这一点非常重要),这是因为我们需要用装饰者对象来替换被装饰者
  2. 在装饰者内部,需要一个抽象父类的引用,这使用了组合的思想,利用组合,可以在被装饰者的基础上增加装饰者的功能,正如装饰者重写的price方法中的写法一样。
  3. 虽然在在装饰者模式中仍然使用了继承机制,但是这里的继承是为了保证所有的子类都拥有相同的父类型,而不是通过继承来获得行为

现在已经成功的将装饰者模式搭建起来了,随着需求的变化动态的增加内容也变得很方便,比如买书还想要附带一个小型书架,这样看书的时候可以不需要用书拿书了,只需要增加一个装饰者SmallBookshelf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public class SmallBookshelf extends BookDecorator {
Book book;
public SmallBookshelf(Book book) {
super();
this.book=book;
}
@Override
public String getDescription() {
return book.getDescription()+",[小型书架]";
}
@Override
public int price() {
return 20+book.price();
}
}

随后你可以任意决定是否为headfirst增加这个装饰,在整个过程中,不需要修改任何的原有代码,这一点体现了一个设计原则:对扩展开发,对修改关闭。

图解装饰者模式

装饰者模式过程
这张图展示了装饰者模式的整个过程,从创建被装饰者对象开始,蓝色箭头表示一步一步对对象进行包装,注意虚线所指示的内容,每一次包装后,在下一个装饰者中的Book对象和前一个不同。是通过组合思想调用前一次包装后的结果的。
橙色箭头和黑色箭头表示返回最终价格的过程,这有点类似递归的过程。

装饰者模式的应用:IO流

Java的IO流是践行装饰者模式的典型实践。以InputStream字节流为例。

首先,了解一下基本的继承关系(但实际上字节流的类复杂得多,以下只列出了一部分)
部分IO流继承关系

InputStream是最上层的父类,表示字节流
FileInputStream是一个具体类,用来对文件进行读取
BufferedInputStream是字节缓冲流,主要是为了减少I/O操作的次数,提高效率。

学习Java基础的过程中,经常会使用BufferedInputStream进行文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class TestIO {
public static void main(String[] args) {
InputStream ins=null;
BufferedInputStream bins=null;
try {
ins=new FileInputStream(new File("E:\\TestFile\\Java\\decoratorIO.txt"));
bins=new BufferedInputStream(ins);
byte buffer[]=new byte[1024]; //缓冲区
int len=bins.read(buffer); //首先读入缓冲区
while(len!=-1) {
System.out.println(new String(buffer));
len=bins.read(buffer);
}
} catch (Exception e) {
}
}
}

通过和买书的例子的对比不难发现,文件读取充满了装饰者模式的影子:
InputStream是顶层的父类(相当于Book)
FileInputStream是一个具体的被装饰者(相当于HeadFirstDesignPattern)
而BufferedInputStream就是一个装饰者,从构造对象的过程可以清晰的看出来;它继承自FilterInputStream,所以FilterInputStream就是所有装饰者共同的父类,它也是InputStream的子类

可以发现,使用过程中装饰者共同的父类是透明的,我们更加关心具体的装饰者。

为什么要拿字节流举例?Java还有一种字符流Reader;
因为字符流的体系里面还涉及到其他的设计模式,并不完全契合今天的主题。

总结装饰者模式

了解了装饰者模式,最后对它有一些总结:

  1. 继承是达到扩展功能的方法之一,但并不总是最优选择
  2. 装饰者和被装饰者应该有共同的父类
  3. 被装饰者可以被大量的装饰者所装饰
  4. 装饰者模式具有运行时行为
  5. 很好的体现了开闭原则:对扩展开放,对修改关闭

本篇文章参考自《Head First 设计模式(中文版)》

smartpig wechat
扫一扫关注微信公众号
0%