一、适配器模式的核心思想
适配器模式的核心思想:把原有的接口转变成调用者期待的接口,从而使不同接口的类可以一起工作。
适配器中包含如下3个角色:
- 源角色 Adaptee:需要适配的目标类或接口。
- 目标角色 Target:所期望得到的接口。
- 适配器角色 Adapter:适配器类是本模式的核心,用来把源接口转换成目标接口,显然这-角色不可以是接口,而必须是具体类。
这3者角色之间的交互关系便组成了适配器模式的模型,如下图所示。
Adaptee 类只有 operation()方法,没有 newoperation()方法,但是客户端又需要目标类同时拥有这两个方法,这时便可以新建一个接口 Target,并提供一个中间环节 Adapter类,Adapter 类实现了 Targe接口,并继承自 Adaptee,Adapter 类的 operation()方法重新封装了 Adapter 类的 operation()方法,并同时实现了 newoperation()方法,这便实现了适配的目的。
二、第一种:类的适配器模式(对类进行适配)
第一种模式是类的适配器模式,它用来对目标类进行包装,如下图所示。
- Source 类是具体的原始类,是待适配的对象,它拥有一个函数operation1()。
- Targetable 是要适配的目标接口,它拥有一个与 Source 同样的接口函数 operation1(),并提供了一个新的接口函数operation2(),该函数是要扩展的功能。
- Adapter 是适配器类,它必须继承Source类,并实现 Targetable接口,从而将 Source 的功能扩展到 Targetable 接口所具有的功能。
适配后的 Source 类,即可以通过调用 Targetable 接口来实现对 Source 类的操作。
下面来看具体的实现。
(1) Source 类的源代码如下程序所示,其默认的操作函数 operation1()用来输出一个字符串
源类 Source.iava
java
package structure.adapter;
/**
* @author Minggg
* 源类
*/
public class Source {
public void operation1(){
System.out.printn("原始类的方法"):
}
}
(2) Targetable必须首先具备与 Source 相同的函数接口,这样它才能够实现 Source 功能,然后增加扩展函数operation2()。其源代码如下程序 所示。
目标接口 Targetable.java
java
package structure.adapter;
/**
* @author Minggg
* 日标接口
*/
public interface Targetable {
/**
* 与源类相同的接口函数事
*/
public void operation1();
/**
* 新的接口函数,源类中没有
*/
public void operation2():
}
(3) Adapter的作用是将 Source 适配为Targetable,因此它必须继承 Source 类并实现 Targetable接口,这样它便拥有了 Source 函数 operation1()的功能,该函数还拥有了与 Targetable 同样的接口;它必须实现 Targetable 的扩展函数,它往控制台输出了一个字符串。其源代码如下程序所示。
适配器类 Adapter.iava
java
package structure.adapter;
/**
* @author Minggg
* 适配器模式,继承源类,并实现目标接口
*/
public class Adapter extends Source implements Targetable {
/**
* 实现目标类的新接口函数
*/
public void operation2() {
System.out.println("适配目标类后的方法");
}
}
(4) 我们就可以创建一个 Adapter 类的对象,该对象属于 Targetable,调用该对象的 operation1()函数将会实现对 Source 函数的调用,调用 operation2()函数将会实现对 Adatper 实现函数的调用,这样就实现了对类 Source 到 Targetable 的适配。测试类源代码如下程序所示。
测试类AdapterTest.java
java
package structure.adapter;
/**
* @author Minggg
* 按照目标接口来创建实例,并调用该接口的各个实现函数
*/
public class AdapterTest {
public static void main(Stringl] args)
// 创建目标接口的类实例
Targetable obj= new Adapter();
// 调用目标类的方法
obj.operation1();
obj.operation2();
}
}
运行该程序的输出为:
java
原始类的方法
适配目标类后的方法
这正是我们想要的结果:把类Source按照接口Targetable 进行调用。
三、第二种:对象的适配器模式(对对象进行包装)
第二种模式是对象的适配器模式,它用来对目标对象进行包装,因此又叫做包装器模式,如下图所示。
- Source 类是具体的原始类,是待包装对象的类,它拥有一个函数operation1()。
- Targetable 是要适配的目标接口,它拥有一个与 Source 同样的接口函数 operation1(),并提供了一个新的接口函数 operation2(),该函数是要扩展的功能。
- Wrapper 是包装器类,它与Adapter适配器不同,它不需要继承Source,但必须拥有一个Source 类型的对象 source,该对象在构造函数中被赋值。在该包装器的operation1()函数中需要调用 source 的 operation1(),这样就实现了对原有对象的调用;同时扩展实现 Targetable的 operation2()函数。
包装后的 Wrapper 类,即可以通过调用Targetable 接口来实现对 Source 类的操作。
以上的 Source 类和 Targetable 接口与第一种模式相同,唯一不同的是 Wrapper 的实现方式不同。其源代码如下程序所示:
包装器类Wrapper.java
java
package structure.adapter;
/**
* @author Minggg
* 包装器模式
*/
public class Wrapper implements Targetable {
private Source source;
/**
* 取得源类对象
*/
public Wrapper(Source source) {
super();
this.source = source;
}
/**
* 调用源类对象的方法
*/
public void operation1() {
source.operation1();
}
/**
* 实现目标类的新接口函数
*/
public void operation2(){
System.out.println("包装目标类后的方法");
}
}
创建一个 Source 类的对象 source,并根据该对象创建一个 Wrapper 包装器obj,该包装器对象属于 Targetable,调用该对象的 operation1()函数将会实现对 Source 函数的调用,调用 operation2()函数将会实现对 Wrapper 实现函数的调用,这样就实现了对类 Source 到 Targetable 的包装。测试类源代码如下程序所示:
测试类WrapperTest.java
java
package structure.adapter;
/**
* @author Minggg
* 按照目标接口来创建实例,并调用该接口的各个实现函数
*/
public class WrapperTest {
public static void main(String[] args){
// 创建源类对象
Source source = new Source();
// 创建source 的包装类对象
Targetable obj= new Wrapper(source);
// 调用目标类的方法
obj.operation1();
obj.operation2();
}
}
运行该程序的输出为:
java
原始类的方法
包装目标类后的方法
输出的结果与第一种模式相同,因此效果是与第一种模式相同的,不同的是适配的方式不同。
四、第三种:接口的适配器模式(对接口抽象化)
有时我们会在一个接口中定义多个接口方法,如果要实现该接口编写一个类,就必须为每一个接口方法编写实现代码,这显然会造成很大的浪费。为了解决这个问题,可以使用第三种适配器模式--默认适配器。它会为原有的接口类实现一个默认的抽象类,在该抽象类中编写每一个接口的默认实现,当我们需要编写一个具体类时,只需要继承自该抽象类,而不需要实现原有的接口。并且,此时我们不需要实现所有的接口方法,只实现需要的函数即可。
如下图所示为接口的适配器模式。
- Sourceable是定义了多个接口函数的接口类。
- DefaultWrapper 是一个抽象类,它实现了接口 Sourcable,并为每一个接口函数提供了默认的实现。
依据 DefaultWrapper 就可以编写不同的实现,在实现中只需要重写部分待实现的函数,而不需要重写全部。
下面来看具体的实现。
(1) Sourcable类的源代码如下程序所示,它定义了两个操作函数。
源接口 Sourcable.java
java
package structure.adapter;
/**
* @author Minggg
* 源接口
*/
public interface Soureable {
public void operation1();
public void operation2();
}
(2) DefaultWrapper 实现了Sourcable 接口,并提供了其两个接口函数的实现,在该实现中可以什么也不做,目的只是为了给其子类提供一个默认的实现。其源代码如下程序所示。
默认适配器类 DefaultWrapper.java
java
package structure.adapter;
/**
* @author Minggg
* 包装器模式
*/
public abstract class DefaultWrapper implements Sourcable {
public void operation1(){}
public void operation2(){}
}
(3) SourceSub1 继承自DefaultWrapper,由于DefaultWrapper 的屏蔽作用,SourceSub1 可以只重新实现自己关心的函数 operation1(),它负责输出一个字符串。其源代码如下程序所示。
源接口的实现子类 SourceSub1.java
java
package structure.adapter;
/**
* @author Minggg
* 源接口的实现类
*/
public class SourceSub1 extends DefaultWrapper {
public void operation1(){
System.out.println("源接口的一个实现子类 Sub1");
}
}
(4) SourceSub2继承自DefaultWrapper,由于DefaultWrapper 的屏蔽作用,SourceSub2 可以只重新实现自己关心的函数 operation2(),它负责输出一个字符串。其源代码如下程序所示。
源接口的实现子类 SourceSub2.java
java
package structure.adapter;
/**
* @author Minggg
* 源接口的实现类
*/
public class SourceSub2 extends DefaultWrapper {
public void operation2(){
System.out.println("源接口的一个实现子类 Sub2");
}
}
以上我们编写了两个实现SourceSub1和SourceSub2,下面分别创建一个对象,创建的对象都属于 Sourcable。然后分别调用它们的方法 operation1()和 operation2()。其源代码如下程序所示。
测试类 DefaultWrapperTest.java
java
package structure.adapter;
public class DefaultWrapperTest {
public static void main(String[] args){
Sourcable source1 = new SourceSub1();
Sourcable source2 = new SourceSub2();
source1.operation1();
source1.operation2();
source2.operation1();
source2.operation2();
}
}
运行该程序的结果如下:
java
源接口的一个实现子类 Sub1
源接口的一个实现子类 Sub2
从输出的结果可以看出,source1和source2仅仅在运行自身实现的函数时发生了作用,对于没有实现的函数则调用了默认适配器 DefaultWrapper 的默认函数,什么也没有输出。
五、何时使用适配器模式
从以上的讲解我们已经知道,如果需要将一个类变成另一个类时就可以使用适配器模式。但是根据需求的不同,可以分别选用3种不同的子模式。
- 类的适配器模式:当希望将一个类转换成满足另一个接口时,可以模仿Adapter的做法来构造一个新的适配器类,该类继承原有的类并实现新的接口即可。
- 对象的适配器模式:当希望将一个对象转换成另一个接口时,可以模仿Wrapper的做法来构造一个新的包装类,该类调用原有的类并实现新的接口即可。
- 默认适配器模式:当不希望实现一个接口的所有方法时,可以模仿DefaultWrapper 的做法构造一个抽象类,给出所有方法的默认的实现,这样,从这个抽象类再继承下去的子类不必实现所有的方法了。
以上的说法稍显抽象,在Java API中有许多地方都运用了适配器模式,下面我们找出来分析下,你就会明白适配器模式的实际用处了。
六、Java 中的应用--Iterator 适配器(对象的适配器模式)
在Java中存在两个迭代器类:Iterator和Enumeration。两者都可以由相应的集合对象转化而来,例如 ArrayList 可以变成 Iterator 对象,Vector 可以变成 Enumeration 对象。然而,有时你可能需要将ArrayList 转换成 Enumeration 对象,这就需要使用包装器模式来将 Iterator 对象转换成 Enumeratior对象了。
此时我们可以选择第二种适配器--对象的适配器。如图下所示,包装器Itermeration 负责将对象Iterator改造成Enumeration的形式。
Iterator 对象拥有如下两个函数:
- hasNext()判断是否有下一个对象。
- next()取得下一个对象。
为了将 Iterator 转换为 Enumeration,此时可以调用上面的两个函数来分别实现 Enumeration 的如下两个函数:
- hasMoreElements()判断是否有下一个对象。
- nextElement()取得下一个对象。
其完整的代码如下程序所示。
Iterator被包装为Enumeration类型Itermeration.java
java
package structure.adapter;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Enumeration;
/**
* @author Minggg
* 使用了包装器模式
*/
public class Itermeration implements Enumeration<Object>{
Iterator<Object> it;
public Itermeration(Iterator<Object> it){
this.it = it;
}
public boolean hasMoreElements(){
return it.hasNext();
}
public Obiect nextElement()throws NoSuchElementException {
return it.next();
}
}
下面来编写测试代码。首先创建一个 ArayList 对象并初始化数据,进而得到Iterator 对象,然后根据该对象构造 Itermeration 的对象即可,最后即可按照 Enumeration 的接口进行迭代。其源代码如下程序所示。
测试类ItermerationTest.java
java
package structure.adapter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
public class ItermerationTest {
public static void main(String[] args){
//创建Iterator 对象
List<Object> list = new ArrayList<Object>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
Iterator<Object> it = list.iterator();
//取得Enumeration 对象
Enumeration<Object> em = new ltermeration(it);
while(em.hasMoreElements()){
Systemout.printn(em.nextElement());
}
}
}
运行该程序的结果如下:
java
aaa
bbb
ccc