参考:
深入分析 Java I/O 的工作机制
Java IO 中的“装饰模式”和“适配器模式”
装饰者模式
参考:图说设计模式–装饰模式
当考虑为一个类增加功能时,我们可以使用继承机制。但是当功能繁多并且要不断嵌套复用时,继承就不是一个好的选择了。对此我们可以采用装饰模式来达到这个目的。即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)
模式UML图
装饰模式包含如下角色:
- Component: 抽象构件(抽象被装饰类)
- ConcreteComponent: 具体构件(具体被装饰类)
- Decorator: 抽象装饰类
- ConcreteDecorator: 具体装饰类
如上图所示,抽象装饰类及其子类会持有一个抽象被装饰类的私有域,并在其继承方法中对应的调用其抽象被装饰类的方法。而具体装饰类则通过继承抽象装饰类后,在对应方法上加上自己的附加行为,完成行为的扩展。由于持有同样的抽象被装饰类,因此这些具体装饰类可以嵌套使用。
模式分析
- 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
- 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
- 对比于静态代理,装饰模式更倾向于不同装饰类间的叠加组合使用,而不是对某一个具体类实行监管。并且装饰类需要传入一个具体被装饰类,而静态代理则在自身内部新建。
适配器模式
参考:
Java与模式之适配器(Adapter)模式
适配器模式可以分为类的适配和对象的适配两种。其目的是为了将两个已经确定的类进行适配转换而存在的—由于无法改动两个已有类,只能通过搭桥的方式进行连通。
类的适配—基于继承
如上图所示,类的适配器用于如下情况:有一客户端类Client实现了Target接口,现有一个Client对象想要调用Adaptee对象,但Adaptee类并未实现Target接口。因此我们可以采用的方法是通过建立一个适配器类Adapter,其继承于Adaptee类,并实现了Target接口。这样就可以让Client对象调用Adapter类从而间接的调用了Adaptee对象了。
对象的适配—基于引用
如上图所示,对象适配器Adapter并不会继承Adaptee,而是在自己内部建立一个Adaptee对象的引用,并在相应的方法上进行直接调用Adaptee对象相应的方法。(类似静态代理)
类适配和对象适配的区别
- 类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。
- 对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理Adaptee的子类了。对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。
- 对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。
- 对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。对于对象适配器,需要额外的引用来间接得到Adaptee。
IO包
Java的IO包具体可以分为流式部分,非流式部分以及其他涉及安全等方面的类。
流式部分
流式部分包括了字节流的读写类和字符流的读写类。字节流常用于图片、音频、视频等非字符持久化数据的读写,字符流则是用于在编程及文本数据的读写。字节流包括了InputStream
和OutputStream
两个抽象类,下面继承了很多具体的类。字符流则包括了Reader
和Writer
两个抽象类。
从InputStream
类的UML视图看看其构成。其余的UML图可以参看:Java IO 知识整理
从上图我们可以看出,InputStream
类就是按照装饰模式进行实现的,首先有一个抽象被装饰类InputStream
,然后又一些具体的被装饰类(eg.FileInputStream
),接着有一个抽象装饰类(FilterInputerStream
)。最后,通过继承该抽象类,又有一系列的具体装饰类。
另一方面,由于有时候会存在要将字节流输入转为字符流输入的情况,因此在Reader
类下存在着一个InputStreamReader
类,该类就是使用了对象的适配器设计模式。通过该类,我们可以将一个字节流输入转换成字符流输入。
部分源码如下:
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}
....
----------------------
public class StreamDecoder extends Reader{
....
private InputStream in;
StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
....
this.in = in;
....
}
从上可以看出,InputStreamReader继承于Reader类,因此可以被其他Reader类的装饰器装饰,而其本身接受的是一个InputStream类的字节流类,通过持有该类的对象从而达到适配器的目的。细心的同学可能发现,StreamDecoder类也是Reader类啊,怎么就是持有了InputStream对象了呢?我们继续看看StreamDecoder类的源码就可以看到,由于要进行转码编译,因此才需要这个类,而这个StreamDecoder类本身是持有InputStream对象的,因此InputStreamReader就间接持有了InputStream对象。
后记:关于常用的网络Socket(属于java.net.*
包)和非流式部分的File
类,RandomAccessFile
类等将在之后的时间在慢慢总结,先挖坑。