假设模型:媒体播放器,既能播放音频文件AudioMedia,又能播放视频文件VideoMedia 。其中音频文件又分MP3格式和WMV格式;视频文件又分MPEG格式和RM格式。
经过功能分析,不管哪种类型的文件都要有一个播放的方法,也就是Play() ,原型结构如图:
当各个类之间继承/实现关系理清楚后,我们现在可以实现一个播放器类MediaPlayer,它可以播放任何一种格式(MP3、WAV、RM、MPEG)的文件。
在此我们要注意一点:在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,而应传递其抽象对象,更好地是传递接口,将实际的调用和具体对象完全剥离开,这样可以提高代码的灵活性。(使用接口有什么好处?那就是你的主程序可以在没有具体业务类的时候,同样可以编译通过。因此,即使你增加了新的业务,你的主程序是不用改动的。)
在本文中体现在MediaPlayer代码中,如下所示:
package adapter_media;
public class MediaPlayer {
public void Play(IMedia media){
media.Play();
}
}
相关代码如下:
package adapter_media;
public interface IMedia {
public void Play();
}
package adapter_media;
public abstract class AudioMedia implements IMedia{
public abstract void Play();
}
package adapter_media;
public abstract class VideoMedia implements IMedia {
public abstract void Play();
}
package adapter_media;
public class MP3 extends AudioMedia {
@Override
public void Play() {
System.out.println("Play AudioMedia with the type of MP3");
}
}
package adapter_media;
public class WAV extends AudioMedia {
@Override
public void Play() {
System.out.println("Play AudioMedia with the type of WAV");
}
}
package adapter_media;
public class MPEG extends VideoMedia {
@Override
public void Play() {
System.out.println("Play VideoMedia with the type of MPEG");
}
}
package adapter_media;
public class RM extends VideoMedia {
@Override
public void Play() {
System.out.println("Play VideoMedia with the type of RM");
}
}
package adapter_media;
/**
*************************************
* @Title MediaPlayer.java
* @Author 张作强
* @Date 2010-8-15
* @Comment 在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,
* 而应传递其抽象对象,更好地是传递接口,
* 将实际的调用和具体对象完全剥离开,这样可以提高代码的灵活性。
*************************************
*/
public class MediaPlayer {
public void Play(IMedia media){
media.Play();
}
}
引入Adapter模式:
原来的RM和MPEG类继承了VideoMedia抽象类,而VideoMedia类又实现了IMedia接口,该接口仅仅提供了Play()方法。现在我们希望为RM,MPEG提供与AudioMedia不同的属性和方法。例如,对于视频媒体而言,应该有一个调整画面大小的方法,如Resize()。而这个方法是IMedia接口所不具备的。
那么怎样为RM,MPEG类提供IMedia接口所不具备的Resize()方法呢?非常自然地,通过这个问题我们就引出Adapter模式的命题了。首先,要假设一个情况,就是原文的所有代码,我们是无法改变的,这包括暴露的接口,类与接口的关系等等,都无法通过编码的方式实现新的目标。只有这样,引入Adapter模式才有意义。
Adapter模式分为两种:类的Adapter模式、对象的Adapter模式。
下面试图根据本例对两种方式进行说明及实现。在实现Adapter模式之前,有必要看看原来的类结构:
一、类的Adapter模式
既然要让RM、MPEG具有Resize()方法,最好的办法就是让它们直接实现IVideoScreen接口。然而受到条件的限制,这两个类的类型是不可修改的。唯一可行的办法就是为相应的类新引入一个类类型,这就是Adapter模式中所谓的Adapter类了。它好比是一个转接头,通过它去实现IVideoScreen接口,同时又令其继承原有的RM或MPEG类,以保留原有的行为。类图如下:
图中的类RMAdapter和MPEGAdapter就是通过Adapter模式获得的对象,它在保留了原有行为的同时,又拥有了IVideoScreen的功能。
相关代码如下:
package adapter_media;
public interface IVideoScreen {
public void Resize();
}
package adapter_media;
public class MPEGAdapter extends MPEG implements IVideoScreen {
public void Resize() {
System.out.println("Play VideoMedia with the size of MPEG");
}
@Override
public void Play() {
super.Play();
}
}
package adapter_media;
public class RMAdapter extends RM implements IVideoScreen {
public void Resize() {
System.out.println("Play VideoMedia with the size of RM");
}
@Override
public void Play() {
super.Play();
}
}
也许很多人已经注意到了,在使用这种方式建立Adapter时,存在一个局限,就是我们必须为每一个要包裹(Wrapping)的类,建立一个相应的Adapter类。如上所述的RM对应RMAdapter,MPEG对应MPEGAdapter。必须如此,为什么呢?虽然RM和MPEG继承了同一个抽象类VideoMedia,但其Play()方法,可能是不相同的。此时,相对应的Adpater类只有直接继承该具体类,方才可以保留其原来的Play()方法的行为本质。