适配器模式
作为两个不兼容的接口之间的桥梁。结构性模式,结合了两个独立接口的功能。有一个电器的插头是三脚的,而现有的插座是两孔的,要使插头插上插座,我们需要一个插头转换器,这个转换器即是适配器。
适配器模式分为三类:类适配器、对象适配器、接口适配器(或又称作缺省适配器模式)
把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作
实现细节
源(Adaptee):需要被适配的对象或类型,相当于插头
适配器(Adapter):连接目标和源的中间对象,相当于插头转换器
目标(Target):期待得到的目标,相当于插座
使用场景
- 系统需要使用现有的类,而此类的接口不符合系统的需要
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口
- 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口)
类适配器与对象适配器的使用场景一致
,仅仅是实现手段稍有区别,二者主要用于如下场景:
- 想要使用一个已经存在的类,但是它却
不符合现有的接口规范
,导致无法直接去访问
,这时创建一个适配器就能间接去访问这个类中的方法
。 - 我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
以上两个场景其实就是从两个角度来描述一类问题,那就是要访问的方法不在合适的接口里,一个从接口出发(被访问),一个从访问出发(主动访问)。
接口适配器使用场景
:
- 想要使用接口中的某个或某些方法,但是
接口中有太多方法
,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口
,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有结构
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配类中,对于客户端而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在不同的系统中复用
- 灵活性和扩展性都比较好,通过使用配置文件,可以方便的更换适配器,也可以在不修改原有代码的基础上增加性的适配器类,完全符合“开闭原则”
具体来说,类适配器模式还有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还有如下优点:
一个对象适配器可以把多个不同的适配者适配到同一个目标;
可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。
缺点
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握
- 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类
对象适配器模式的缺点如下:
与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂
Demo
笔记本通过读卡去读取TF卡
- 创建一个SD卡的接口和具体实现类,有读写的功能
public interface SdCard {
/** 读取SD卡方法 */
String readSD();
/** 写入SD卡功能 */
int writeSD(String msg);
}
class SdCardImpl implements SdCard {
public String readSD() {
String msg = "sdcard read a msg :hello word SD";
return msg;
}
public int writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
return 1;
}
}
- 创建计算机类,用于进行SD卡的读取操作,计算机接口可以直接操作SDcard类
public interface Computer {
String readSD(SdCard sdCard);
}
class ThinkPadComputer implements Computer {
public String readSD(SdCard sdCard) {
if (sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}
- 计算机可以直接操作进行SD卡的读取
public class Demo1 {
public static void main(String[] args) {
/** 通过电脑直接读取SD卡中的信息*/
Computer computer = new ThinkPadComputer();
SdCard sdCard = new SdCardImpl();
System.out.println(computer.readSD(sdCard));
}
}
- 现在又出现了另外一种TF卡,同样也具备读写功能
public interface TfCard {
String readTF();
int writeTF(String msg);
}
class TfCardImp implements TfCard {
public String readTF() {
String msg = "tf card reade msg : hello word tf card";
return msg;
}
public int writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
return 1;
}
}
在不改变计算机读取SD卡接口的情况下,如何使计算机成功的读取TF卡呢?这里可以考虑使用适配器模式。
SdCard:Target目标抽象类,最终完成功能的类,相当于插座,,用户需要直接引用该类
TfCard:Adaptee需要被适配的对象或类型,相当于插头,,用户不可以直接引用该类,需要进行适配
Adapter类就是一个模块,将TfCard具体功能(实现类)包装成SdCard对外提供。此处的包装可以使用组合或者继承的方式。
使用适配器时,上面的SdCardImpl就用不着了,Adapter实际上就是Target的实现类。所以Target本身可以设计为一个适配器接口
类适配器
单一的为某个类实现适配,这里是将TfCardImp适配为SdCard;
通过继承适配者进行实现,适配器类需求实现Targert接口(SdCard),并继承Adaptee(TfCard)的实现类
/**
* @desciption: SD兼容TF,相当于读卡器,继承SdCard并持有TfCard,对象适配器
*/
public class SdAdapter2Tf extends TfCardImp implements SdCard {
/** 像这样,通过继承适配者类,通过调用父类的方法实现功能,为类适配器*/
public String readSD() {
System.out.println("adapter read tf card ");
return super.readTF();
}
public int writeSD(String msg) {
System.out.println("adapter write tf card");
return super.writeTF(msg);
}
}
public class Demo1 {
public static void main(String[] args) {
Computer computer = new ThinkPadComputer();
System.out.println("------------类适配器----------------");
SdCard adapter2Tf = new SdAdapter2Tf();
System.out.println(computer.readSD(adapter2Tf));
}
}
对象适配器
通过引用适配者进行组合实现,适配器类需求实现Targert接口(SdCard),并持有Adaptee(TfCard)对象
/**
* @desciption: SD兼容TF,相当于读卡器,继承SdCard并持有TfCard,对象适配器
*/
public class SdAdapter1Tf implements SdCard {
/** 像这样,通过组合的方式持有适配者类,也就是是配置者类是适配器的一个属性,为对象适配器*/
private TfCard tfCard;
public SdAdapter1Tf(TfCard tfCard) {
this.tfCard = tfCard;
}
public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}
public int writeSD(String msg) {
System.out.println("adapter write tf card");
return tfCard.writeTF(msg);
}
}
public class Demo1 {
public static void main(String[] args) {
Computer computer = new ThinkPadComputer();
/** 电脑无法直接读取TF卡中的信息,需要一个适配器来读取*/
System.out.println("------------对象适配器----------------");
TfCard tfCard = new TfCardImp();
SdCard adapter1Tf = new SdAdapter1Tf(tfCard);
System.out.println(computer.readSD(adapter1Tf));
}
}
接口适配器
通过抽象类来实现适配,假如有一种情况,Target目标抽象类,例中的SdCard接口定义了N多个接口,而我们现在却只想使用其中的一个或者几个方法,如上面两个例子中实现的适配器,那我们就需要将SdCard中的所有方法均实现,哪怕只是空的实现,也需要在适配器中去写相应的代码,这将会导致这个Adapter类变得臃肿,调用也不方便。这时我们可以使用抽象适配器作为中间件,用这个抽象类实现接口并提供默认的方法,而真正的Adapter仅仅需要继承该抽象类,重写我们关注的方法即可。
以上文中的SdCard为例,假如我们在使用时仅关注计算机读取卡操作,而关心写操作,则直接实现SdCard,需要重写读写两个方法,即使写操作不用,我们也要在该类中存在一个空的写方法。
首先,可以创建一个抽象类实现SdCard类,并提供默认的方法:
抽象适配器:实现接口并提供所有的默认方法
public abstract class AbstraceSdCard implements SdCard {
public String readSD() {
return "";
}
public int writeSD(String msg) {
return 1;
}
}
仅关心其中某一个方法,此时只实现一个方法即可。
public class SdCardAdapter extends AbstractSdCard {
private TfCard tfCard;
public SdCardAdapter(TfCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
System.out.println("11111111111111");
System.out.println("22222222222222");
System.out.println("33333333333333");
return tfCard.readTF();
}
}
public class Demo1 {
public static void main(String[] args) {
Computer computer = new ThinkPadComputer();
System.out.println("------------接口适配器----------------");
TfCard tfCard2 = new TfCardImp();
SdCard sdCard1 = new SdCardAdapter(tfCard2);
System.out.println(computer.readSD(sdCard1));
}
}