设计模式--适配器模式
1、定义
适配器模式将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
2、模式中涉及的角色
目标接口:与客户接触的接口,换句话说,客户只会使用这个接口
被适配接口:需要转换成客户所期望的那样的接口
适配器:通过包装一个需要适配的接口,把该接口转换成目标接口
3、适配器的类型
类适配器:继承被适配者和目标类(多继承)来实现
对象适配器:使用组合来适配被适配者来实现
由此可见,类适配器是通过多重继承来实现的,而在java中这是不可能的,所以,这里只谈对象适配器
4、适配器模式(对象)类图
5、现实应用与举例
有这么一个情景:一个客户拥有三台设备,分别是小米5、苹果手机、MP3,当他想为这三个设备充电时就得拥有三条对应的充电线,即各自的线的接口只适合自己用,不适合其他设备用。代码如下:
/**
* 不用适配器的情况
* @Author 先
* @ClassName Client_NotAdapter.java
* @Time 2017年3月11日 下午9:33:58
*/
public class Client_NotAdapter {
public static void main(String[] args) {
//给小米5充电,生成小米5专用充电线
ElectryLine mi_line = new Xiaomi5();
mi_line.electricize();
//给苹果手机充电,生成苹果手机专用充电线
ElectryLine apple_line = new Apple();
apple_line.electricize();
//给MP3充电,生成MP3专用充电线
ElectryLine mp3_line = new MP3();
mp3_line.electricize();
}
}
/**
* 充电线
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:33:06
*/
interface ElectryLine{
//充电方法
void electricize();
}
/**
* 小米5手机
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:36:14
*/
class Xiaomi5 implements ElectryLine{
@Override
public void electricize() {
System.out.println("小米5正在充电……");
}
}
/**
* 苹果手机
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:37:15
*/
class Apple implements ElectryLine{
@Override
public void electricize() {
System.out.println("苹果手机正在充电……");
}
}
/**
* MP3
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:38:05
*/
class MP3 implements ElectryLine{
@Override
public void electricize() {
System.out.println("MP3正在充电……");
}
}
上述做法的缺点:
1、客户得创建三个对象(充电线),耗费内存空间多
2、没有将目标类与适配者类解耦
而使用适配器模式,就可以只用一条充电线,给三台不同接口的设备充电,就像这张图(网上随便找的一张)
这张图很好的解释了适配器的定义与应用,代码如下:
/**
* 适配器模式
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:26:52
*/
public class Client_Adapter {//客户
public static void main(String[] args) {
Xiaomi5 xiaomi = new Xiaomi5();
Apple apple = new Apple();
MP3 mp3 = new MP3();
//这句话可以这么理解:充电线的引用指向了适配器,相当于将充电线插到转接头上
ElectryLine line = new Adapter(xiaomi,apple,mp3);
//如果客户想给小米5充电
line.electricize("xiaomi5");
//如果客户想给苹果手机充电
line.electricize("apple");
//如果客户想给MP3充电
line.electricize("mp3");
//总结分析:整个过程,客户只创建了一个充电线的对象,却通过了适配器(即转接头)给三台不同的设备充电了
//实际上,这个适配器只是把一个接口转换成另一个接口了(并没有同时转换成三个接口),
//只不过是通过传参的形式来判断到底要转换成哪个接口,转换的只是小米5或者苹果或者MP3
//本里这样子写只是为了让读者更好地理解适配器
}
}
/**
* 充电线
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:33:06
*/
interface ElectryLine{
//充电方法
void electricize(String type);
}
/**
* 小米5手机
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:36:14
*/
class Xiaomi5{
public void mi_electry(){
System.out.println("小米5正在充电……");
}
}
/**
* 苹果手机
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:37:15
*/
class Apple{
public void apple_electry(){
System.out.println("苹果手机正在充电……");
}
}
/**
* MP3
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:38:05
*/
class MP3{
public void mp3_electry(){
System.out.println("MP3正在充电……");
}
}
/**
* 这里假设这个适配器就只是转接小米5、苹果和MP3的
* @Author 先
* @ClassName Client_Adapter.java
* @Time 2017年3月11日 下午8:40:15
*/
class Adapter implements ElectryLine{
//对象适配器使用的是组合语法
Xiaomi5 xiaomi;
Apple apple;
MP3 mp3;
//用构造器取得被适配对象的引用
public Adapter(Xiaomi5 xiaomi,Apple apple,MP3 mp3){
this.xiaomi = xiaomi;
this.apple = apple;
this.mp3 = mp3;
}
@Override
public void electricize(String type) {
switch(type){
case "xiaomi5" : xiaomi.mi_electry();break;
case "apple" : apple.apple_electry();break;
case "mp3" : mp3.mp3_electry();break;
}
}
}
分析:
看起来使用了适配器模式后代码变多了,但是将目标类与适配者类分开了,即将充电线与手机分开,客户指操作一条线,中间通过转接口(适配器)来给不同的设备充电,对于客户来说,体验好了,从内存的角度来说,也节省了空间。正如《Thinking in java》中所说--“适配器接受你任意的接口类型,并且产生你需要的接口类型”,这里就验证的这句话,转接头(适配器)接受不同的设备接口,生成客户想要的那个接口(目标接口ElectryLine)。
再用代码来解释本文上述的模型图:
/**
* 目标接口
* @Author 先
* @ClassName Client_NotAdapter.java
* @Time 2017年3月11日 下午11:18:35
*/
interface Target{
void request();
}
/**
* 被适配类
* @Author 先
* @ClassName Client_NotAdapter.java
* @Time 2017年3月11日 下午11:19:53
*/
class Adaptee{
public void specificRequest(){}
}
/**
* 适配器
* @Author 先
* @ClassName Client_NotAdapter.java
* @Time 2017年3月11日 下午11:21:19
*/
class Adapter implements Target{
Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
6、总结:
优点:
1、通过适配器,客户可以调用同一个接口,来操作本来接口不匹配的对象,而这些实现对客户是透明的。
2、复用了现存的类,解决了现存类和复用环境要求不一致的问题。
3、将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
4、一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
总而言之,就是
更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
缺点:
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。