设计模式-适配器模式

一、适配器模式的核心思想

适配器模式的核心思想:把原有的接口转变成调用者期待的接口,从而使不同接口的类可以一起工作。

适配器中包含如下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
相关推荐
思忖小下9 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo201711 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
aaasssdddd9613 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-113 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下15 小时前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风16 小时前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'18 小时前
23种设计模式
android·java·设计模式
NorthCastle1 天前
设计模式-创建型模式-简单工厂模式详解
设计模式·简单工厂模式
越甲八千1 天前
重拾设计模式-外观模式和适配器模式的异同
设计模式·适配器模式·外观模式
越甲八千1 天前
重拾设计模式--适配器模式
设计模式·适配器模式