本篇主要讲述适配器设计模式。
1日常生活中的适配器
适配器的例子在日常生活中随处可见。
例如:中国的电源电压为220V,而日本的电源电压110V,在国内使用日本原装电器时,就必须有一个电源适配器将220V的电压适配至110V。
新的电脑鼠标一般都是USB接口,而旧的电脑机箱上根本就没有USB接口,而只有一个PS2接口,这时就必须有一个PS2转USB的适配器,将PS2接口适配为USB接口。
一般家庭中电源插座有的是两个孔(两项式)的,也有三个孔(三项式)的。很多时候我们可能更多地使用三个引脚的插头,但是那种两孔的插座就不能满足我们的需求,此时我们一般会买一个拖线板,该拖线板的插头是是两脚插头,这样就可以插入原先的两孔插座,同时拖线板上带有很多两孔、三孔的插座!这样不仅可以扩容,更主要的是将两孔的插座转变为三孔的插座!
图1为一个将220v电源转换为5v电源的适配器,图2为将PS2接头转换为USB接头的适配器。
图 1 电源适配器 图2鼠标适配器哦
设计模式里的适配器模式和日常生活中的适配器的作用是完全一样的。下面请看设计模式中的适配器模式吧!
2 Adapter模式
适配器模式俗称变压器模式,指将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。从功能上讲这些接口不兼容的类一般具有相同或相似的功能。通常我们通过修改该类的接口来解决这种接口不兼容的情形,但是如果我们不愿意为了一个应用而修改各原有的接口,或者我们压根就没有原有对象的源代码那该怎么办呢? 此时Adapter模式就会派上大用场了。
2.1 适配器模式的几个要素
适配器模式所涉及的角色包括:1.源;2.目标;3.适配器;4.客户。
1. 源(Adaptee):已经存在的、需要适配的类。
2. 目标(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
3. 适配器(Adapter):适配器模式的核心类。有两种方式实现Adapter,对象适配器(Object Adapter)和类适配器(Class Adapter)。
4. 客户(Client):
下图是Adapter设计模式中的几个对象:
2.2 Adapter模式的使用场景和意图
“The Adapter Pattern converts the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.”
--GOF 《Element of reusable Object-Oriented Software》
上面是GOF在《可复用元素面向对象》这本书中对适配器模式的概括。很经典的一句话。
从上图我们可以看出Adapter模式使用的场景:我们的新系统中有一个客户(Client)需要一个Target的接口,原有的系统中有一个现成的类(Adaptee),该类具有Target接口所需的功能。但是Adaptee类提供的接口却与客户需要的Target接口不同。在这种情况下,我们希望将现有的接口(Adaptee)转化(convert)为客户类期望的接口(Target),这样不仅保证没有修改原有的系统,更主要的是还能保证对现有类的重用。若不进行这样的转化,客户类就不能利用现有类所提供的功能。
因此在客户需要的接口Target和原系统中具有此功能的类Adaptee间必然需要一个“调停者”对象,根据该对象的功能可以确定此“调停者”必须具有以下两个特点:1.实现Target接口;2.重用Adaptee。该“调停者”就是适配器模式的主角――Adapter类。首先他实现了客户需要的Target接口,因此他可以满足客户(Client)的需要,同时它又重用的原有系统的Adaptee类。
在前面我们讲过在Java中有两种方式可以达到重用的目的:1.继承;2.包含。同样在适配器模式中也可以通过这两种方式达到重用Adaptee类的目的。根据重用使用方式的不同一般将适配器模式分为两类:类适配器(Class Adapter)和对象适配器(Object Adapter)。
2.3 类适配器 VS 对象适配器 (尽量用对象适配器,多用合成/聚合、少用继承)
要正确地区别这两种适配器的区别,我们还是从一个简单的例子开始吧!我们的系统中有一个具有某个特定功能的类Adaptee,一个客户类Client――他需要一个实现Target接口的对象,和一个Target接口,以下是他们的源码:
//Adaptee.java public class Adaptee{ public void specialRequest(){ System.out.println("Called SpecificRequest() in Adaptee "); } } //Client.java public class Client { public static void main(String[] args){ Target t = …… //new Adapter() ; t.request(); } } //Target.java public interface Target{ public void request(); }
根据上一小节的分析我们知道此时需要一个Adapter对象,该对象实现Target接口,同时他又重用现有的Adaptee类。任何有一点点OO(面向对象)知识的人都会想到通过继承可以达到重用的目的。下面是通过继承实现Adaptee类重用的例子:
//Adapter.java public class Adapter extends Adaptee implements Target{ public void request(){ this.specialRequest(); } }
简单明了!现在的过程就是:客户调用Target接口的request方法,实际就是调用其父类Adaptee的specialRequest方法。图4为类适配器(Class Adapter)的类图:
这就是大家通常常说的类的适配器!类适配器具有以下的两个特点:1.适配器类(Adapter)实现Target接口;2. 适配器类(Adapter)通过继承来实现对Adaptee类的重用。
下面是一个通过组合关系实现继承的例子,以下是源码:
//Adapter.java public class Adapter implements Target{ Adaptee adaptee = new Adaptee(); public void request(){ adaptee.specialRequest(); } }以上就是通过组合实现对Adaptee类重用的例子。这就是大家通常说的对象的适配器!对象的适配器具有以下的两个特点:1.适配器类(Adapter)实现Target接口;2. 适配器类(Adapter)通过组合来实现对Adaptee类的重用。图5是对象适配器的类图。
对于这两者不同的适配器客户代码其实是完全一样的。以下是客户的代码:
//Client.java public class Client { public static void main(String[] args){ Target t = new Adapter() ; t.request(); } }
2.4 适配器模式的变体
上面谈到的所有适配器都是通过重用Adaptee类从而实现Target接口中的方法。这只是具体层次的重用。是否还存在更高层次的适配器呢!?答案是肯定的!我们不仅可以适配具体的类还可以适配抽象类,甚至适配接口!下面是这种适配的简图:
从上图可以看出:Adapter将Adaptee接口适配为客户Client需要的接口Target,这样在整个系统中所有实现Adaptee接口的类都可以通过Adapter适配为Target对象,从而避免为每一个类都写一个适配器。后面会给大家带来一个JDK中使用此中适配的例子。我们不仅仅可以象上面一样对接口进行适配,也可以对抽象类进行适配!主要是根据系统的需求,确定此时的场景是否适合使用适配器模式!
以上是适配器模式!关于IO的适配在下面的源代码分析中,会阐述byte数组到InputStream的适配。就当作一个适配器的例子吧!其它关于适配器在Java中的应用以及如何将java.sql.ResultSet适配至javax.swing.table.TableModel、将File、DOM树、Thread适配至javax.swing.tree.TreeModel就不在这里详细讲解了
转自: http://miaoxiaodong78.blog.163.com/blog/static/1876513620070123473737/