a.适配器模式(结构型模式核心)
所谓的适配器模式,就是适配器类继承/实现目标系统类/接口,当客户端访问适配器继承/实现的目标系统类/接口的对应方法的时候,表面上看是访问了适配器父类的目标系统的类/接口的方法,实际上适配器类对目标系统的类/接口的方法进行“转接”,实质上访问的是老系统的类/接口的方法。
通过上述适配器模式的描述,有三个角色:
新(目标)系统角色:类/接口
老系统角色:类/接口
适配器角色:肯定是类,并且前提是继承/实现了目标系统的类/接口
针对于目标系统角色来讲,适配器类与目标系统角色无非是下面两种之一:

而唯一可变的就是适配器类与老系统之间的关系,要么就是下面的一种情况,适配器类实现/继承老系统:

要么就是适配器类关联老系统,持有老系统的实例对象:

我们可以通过一张表来表示适配器与目标系统和老系统类或者接口的8种关系:
老系统类继承 | 老系统接口实现 | 老系统类关联 | 老系统接口关联 | |
目标系统类继承 | 继承-继承 | 继承-实现 | 继承-关联 | 继承-关联 |
目标系统接口实现 | 实现-继承 | 实现-实现 | 实现-关联 | 实现-关联 |
对于适配器的所有情况来看,都超不出上述的8种关系。
由设计模式的分类可知,处理类之间的关系的是类型模式,处理对象之间组合关系的是对象型模式,因此上表中的左面两列:老系统类继承,老系统接口实现这两列叫做类适配器,而右侧的剩余的与老系统接口/类关联的叫做对象适配器。
1.类适配器
类适配器如上面的一共有四种情况,无非就是类的继承和接口的实现与老系统和新系统之间的组合,因此这里只选择一种进行类图分析和源代码分析,其余几种都是大同小异。
a.类图

如上图所示,客户端访问目标系统的sayhello方法,实际上就是访问适配器的sayhello方法,在适配器Adapter类的sayHello方法中进行了转接,调用老系统OldSystem类的sayhi方法,这次转接就是一个适配的过程。
b.代码
1.TargetSystem.java(目标系统接口)
public interface TargetSystem {
public abstract void sayHello();
}
2.Adapter.java(适配器类)
public class Adapter extends OldSystem implements TargetSystem {
public void sayHello() {
sayhi();//进行适配的功能
}
public void sayhi() {
// TODO Auto-generated method stub
System.out.println("-----sayhi------");
}
}
3.OldSystem.java(老系统类)
public class OldSystem {
public void sayhi(){
System.out.println("---------------");
}
}
4.Client.java(客户端)
public class Client {
public static void main(String[] args){
TargetSystem ts = new Adapter();
ts.sayHello();//这个其实访问的是ts.sayhi()
}
}
2.对象适配器
针对于上述的类适配器,我们从具体使用的情况设计模式的6条基本原则的第六条合成复用原则可以得知,依赖 》关联》泛化(实现),关联比泛化(实现)耦合度低,因此对象适配器从后面章节的使用程度来看是比较高的,符合高内聚低耦合的设计模式的指导思想。
那么反过来如果说类适配器的使用场景,那么肯定是右侧老系统角色无法实例化对象,换句话说,也就是下面两种情况使用类适配器:
1.右侧的老系统类不能实例化
2.右侧的老系统接口的子实现类没有,或者有子类但是不能实例化
这样如果使用对象适配器模式,由于适配器类和老系统类/接口是关联关系,因此做接口的转接的话,必须实例化,所以可以见得上述两种情况,肯定不能用对象适配器。
由于上述两种情况还是比较少见,因此除了上述两种情况,根据复用合成原则,我们考虑还是使用对象适配器。
a.类图

b.代码
1.TargetSystem1.java(目标系统接口)
public interface TargetSystem1 {
public abstract void sayHello();
}
2.Adapter1.java(适配器类)
public class Adapter1 implements TargetSystem1 {
private OldSystem1 oldSystem1;
public Adapter1(){
oldSystem1 = new OldSystem1();
}
public void sayHello() {
oldSystem1.sayHi();
}
}
3.OldSystem.java(老系统类)
public class OldSystem1 {
public void sayHi() {
System.out.println("OldSystem1----sayHi");
}
}
4.Client1.java(客户端)
public class Client1 {
public static void main(String[] args){
TargetSystem1 ts1 = new Adapter1();
ts1.sayHello();
}
}
3.各消息中间件的对象适配器

如图上图是最普通的消息中间件的应用架构图。
对于右侧是消息中间件的适配器部分,可以看到这里的目标系统接口是MQService类,这里面有很多契约的方法,必须由适配器实现,例如open,close,putfile,closefile等方法,而适配器类的是IBMMQService类,它适配的是IBM的MQ中间件包的IBMMQOperate类,这个
IBMMQOperate类是ibm官方的包,很多接口都和目标系统接口MQService类不一致,因此这里通过IBMMQService适配器类进行转接。
可以看出的是,如果不和ibm合作,和其它消息中间件厂商合作,那么你还需要写一个适配器,类似的ActiveQService类,与apache包的ActiveQOperate消息处理类进行适配。
最后说的一点是,左面是定时任务,是对各个目录的扫描,然后将这些目录下的文件根据业务逻辑放到通过MQService接口,也就是消息中间件的适配器上,最后放到应用服务器的队列上面。
4.javaAPI的IO包的适配器
首先,为了介绍IO包,首先先看看IO包中的小知识。
针对于javaAPI的流处理,一共分成两种流:
一种是字节流,抽象类为InputStream OutputStream,
具体实现为FileInputStream FileOutputStream
一种是字符流,抽象类为Reader Writer
具体实现为FileReader FileWriter
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串;而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点.
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。
可以从两点找出字节流和字符流之间的区别
1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
2.字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
对于上述的两种流,要想将其互相转化,sun在这里使用了适配器模式,而这个“著名”的对象适配器类就是:InputSteamReader类和 OutputStreamWriter 类。
类图举例如下:


我们可以看一个例子:


5.默认适配器
对于类适配器模式而言,如果将适配器类与老系统类之间的继承关系倒转过来,那么这个就是默认适配器了。
对于默认适配器来说,通常是目标系统接口的方法太多,如果你直接实现这个接口的话,那么你必须每一个方法都要实现一遍,而可能你只需要用到一两个方法,这时候默认适配器就派上了用场,它在中间起到一个将所有目标系统接口的方法都实现一遍,里面都是空的实现,这样你不是去实现接口了,你只需继承这个默认的适配器就可以完成了,而且你需要实现哪个方法覆盖即可。
a.类图

b.代码
1.Listener.java(目标系统接口)
public interface Listener {
public abstract void beforeListener();
public abstract void afterListener();
public abstract void onListener();
}
2.AbsListenerAdapter.java(默认适配器)
public class AbsListenerAdapter implements Listener {
public void beforeListener() {
}
public void afterListener() {
}
public void onListener() {
}
}
3.MyListener.java(实现类)
public class MyListener extends AbsListenerAdapter {
public void onListener() {
System.out.println("这里只需要实现一个onListener方法就可以了");
}
}
4.Client.java(客户端)
public class Client {
public static void main(String[] args){
Listener l = new MyListener();
l.onListener();
}
}
总结:
适配器模式,分为类适配器和对象适配器,之所以叫类适配器是因为在系统转接的过程中通过继承类来实现的,没有创建对象,所以叫做类适配器;而对象适配器是通过对象的关联来实现的,需要用到对象,所以叫做对象适配器;适配器是子系统转接的时候最常用的模式,通常用于中间件领域的消息适配,java io包中的字节流和字符流两系统的转化适配。




