1.定义
将一个类的接口转换成客户所希望的另一个接口,Adapter模式使得那些原本因为接口不兼容而不能一起工作的那些类可以一起工作。
2.使用场景
一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。1.封装有缺陷的接口设计,2.统一多个类的接口设计,3.替换依赖的外部系统,4.兼容老版本接口,5.适配不同格式的数据。
例如: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
3.实现
/**
* 目标(Target):目标是一个接口,该接口是客户想要使用的接口。
* @author Administrator
*
*/
public interface MediaPlayer {
void play(String autioType,String name);
}
/**
* 被适配者(Adaptee):被适配者是一个已经存在的接口或抽象类,这个接口或抽象类需要被适配。
* @author Administrator
*
*/
public interface AdvanceMediaPlayer {
void playVlc(String audioType,String name);
void playMp4(String audioType,String name);
}
/**
* 被适配者子类VlcMediaPlayer:实现父类playVlc方法
* @author Administrator
*
*/
public class VlcMediaPlayer implements AdvanceMediaPlayer{
@Override
public void playVlc(String audioType, String name) {
// TODO Auto-generated method stub
System.out.println("vlc player:"+name);
}
@Override
public void playMp4(String audioType, String name) {
// TODO Auto-generated method stub
//nothing
}
}
/**
* 被适配者子类Mp4MediaPlayer:实现父类playMp4方法
* @author Administrator
*
*/
public class Mp4MediaPlayer implements AdvanceMediaPlayer{
@Override
public void playVlc(String audioType, String name) {
// TODO Auto-generated method stub
//do nothing
}
@Override
public void playMp4(String audioType, String name) {
// TODO Auto-generated method stub
System.out.println("mp4 player:"+name);
}
}
/**
* 适配器(Adapt):适配者是一个类,该类实现了目标接口并包含有适配者的引用,即适配者的职责是对被适配者接口(抽象类)与目标接口进行适配。
* @author Administrator
*
*/
public class MediaAdapter implements MediaPlayer{
AdvanceMediaPlayer adMediaPlayer;
public MediaAdapter(String audioType) {
// TODO Auto-generated constructor stub
if(audioType.equalsIgnoreCase("vlc")) {
adMediaPlayer = new VlcMediaPlayer();
}else if(audioType.equalsIgnoreCase("mp4")){
adMediaPlayer = new Mp4MediaPlayer();
}
}
@Override
public void play(String autioType, String name) {
// TODO Auto-generated method stub
if(autioType.equalsIgnoreCase("vlc")) {
adMediaPlayer.playVlc(autioType, name);
}else if(autioType.equalsIgnoreCase("mp4")) {
adMediaPlayer.playMp4(autioType, name);
}
}
}
/**
* AudioPlayer:目标接口的子类,实现目标接口,并支持被适配类
* @author Administrator
*
*/
public class AudioPlayer implements MediaPlayer{
MediaAdapter adapter;
@Override
public void play(String autioType, String name) {
// TODO Auto-generated method stub
if(autioType.equalsIgnoreCase("mp3")) {
System.out.println("mp3 Player:"+name);
}else if(autioType.equalsIgnoreCase("vlc")||autioType.equalsIgnoreCase("mp4")) {
adapter = new MediaAdapter(autioType);
adapter.play(autioType, name);
}else {
System.out.println("file not support!!");
}
}
}
/**
* 测试类:使用AudioPlayer支持vlc和mp4
* @author Administrator
*
*/
public class Test {
public static void main(String[] args) {
MediaPlayer mPlayer = new AudioPlayer();
mPlayer.play("mp3", "aaa.mp3");
mPlayer.play("mp4", "bbb.mp4");
mPlayer.play("vlc", "ccc.vlc");
mPlayer.play("avi", "eee.avi");
}
}
运行结果
4.总结
主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
5.扩展
适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。具体的代码实现如下所示。其中,ITarget表示要转化成的接口定义。Adaptee是一组不兼容ITarget接口定义的接口,Adaptor将Adaptee转化成一组符合ITarget接口定义的接口。
// 类适配器: 基于继承
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor extends Adaptee implements ITarget {
public void f1() {
super.fa();
}
public void f2() {
//...重新实现f2()...
}
// 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}
// 对象适配器:基于组合
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor implements ITarget {
private Adaptee adaptee;
public Adaptor(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void f1() {
adaptee.fa(); //委托给Adaptee
}
public void f2() {
//...重新实现f2()...
}
public void fc() {
adaptee.fc();
}
}
针对这两种实现方式,在实际的开发中,到底该如何选择使用哪一种呢?判断的标准主要有两个,一个是Adaptee接口的个数,另一个是Adaptee和ITarget的契合程度。
1、如果Adaptee接口并不多,那两种实现方式都可以。
2、如果Adaptee接口很多,而且Adaptee和ITarget接口定义大部分都相同,那我们推荐使用类适配器,因为Adaptor复用父类Adaptee的接口,比起对象适配器的实现方式,Adaptor的代码量要少一些。
3、如果Adaptee接口很多,而且Adaptee和ITarget接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。