适配器模式(Adapter)是23种设计模式之一。DP中是这么定义外观模式的:
适配器模式将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能在一起工作的那些类可以在一起工作。
我们生活中就经常使用到适配器,适配器这个词最早应该是出现在电工学里。有些国家用110V电压,而我国使用的是220V电压,但我们的电器,例如手机、笔记本电脑、平板电脑等,是不能什么电压都能用的,于是我们就需要使用电源适配器,这样一来只要是电,不管多少伏都能适配成需要的电压。而适配器模式就是起到这种作用,将既有的,但是不能够直接使用的,也无法进行改造的,通过适配后,让它能够被使用。
在软件开发中,也就是系统的数据和行为都正确,但是接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。
在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式,由于类适配器模式是通过多重继承对一个接口与另一个接口进行匹配,而Java语言不支持多继承,所以这里只介绍对象适配器模式。
适配器模式(Adapter)结构图:
使用简单的代码来实现这个模式的结构:
Target类:
代码语言:javascript复制package org.zero01.target;
/**
* 客户所期望的接口
*/
public class Target {
public void request() {
System.out.println("普通请求!");
}
}
Adaptee类:
代码语言:javascript复制package org.zero01.adapter;
/**
* 需要适配的类
*/
public class Adaptee {
public void specificRequest() {
System.out.println("特殊请求!");
}
}
Adapter类:
代码语言:javascript复制package org.zero01.adapter;
import org.zero01.target.Target;
/**
* 适配器类
*/
public class Adapter extends Target {
private Adaptee adaptee = new Adaptee();
// 把request方法的调用转换成specificRequest方法的调用
public void request() {
adaptee.specificRequest();
}
}
客户端:
代码语言:javascript复制package org.zero01.client;
import org.zero01.adapter.Adapter;
import org.zero01.target.Target;
public class Client {
public static void main(String[] args) {
Target target=new Adapter();
// 客户端调用target的request方法即可
target.request();
}
}
什么时候适合用适配器模式:
1.想使用一个已经存在的类,但是它的接口,也就是方法与你要求的不同时可以考虑使用适配器模式。也就是说两个类所做的事情相似或相同,但是具有不同的接口时可以使用适配器模式。使用了适配器模式后,类之间就会共享同一个接口,使得客户端代码只需要统一调用同一个接口即可,这样可以使客户端的代码更简单、直接、紧凑。
2.软件开发后期或维护期适合使用适配器模式,因为维护时会由于开发人员的不同,而造成功能类似,但接口不同的情况,此时就适合使用适配器模式。
3.设计一个系统时,如果使用了第三方的组件,而这个组件的接口与我们自己的系统接口不同,但是我们又没有必要为了迎合它而改动自己的接口,这种情况也可以考虑使用适配器模式来解决接口不同的问题。
什么时候不适合用适配器模式:
在设计初期时,就应该统一好接口的定义,尽量不要把接口设计成不同的,以及需要规范好类与方法的命名,所以在这种开发前期的情况下,如果出现接口不同,应该是将不同的接口重构成统一的接口,而不是考虑使用适配器模式。适配器模式应该用在双方都不太容易修改接口的时候再使用适配器模式进行适配。
所以我们开发时应该事先预防接口的不同的问题,这样不匹配的问题就不会发生。在有小的接口不统一而引起问题时,应该及时对接口进行重构,这样问题不至于扩大。只有碰到无法改变原有的设计和代码的情况时,才考虑适配。事后控制不如事中控制,事中控制不如事前控制。适配器模式虽好,但是如果无视它的应用场合而盲目使用,就是本末倒置了。
简单的适配器模式示例:
我这里之前写了一个数码产品充电的系统,但是我现在想要加入一个小灯泡,但是小灯泡与数码产品实现的方法不一样,但是它们同样需要使用电,所以这时候就可以使用适配器了。
结构图如下:
数码产品接口,定义一个充电方法:
代码语言:javascript复制package org.zero01.target;
public interface DigitalProducts {
public void charge();
}
手机、平板以及笔记本电脑都实现了这个接口:
代码语言:javascript复制public class Phone implements DigitalProducts{
public void charge() {
System.out.println("手机使用4v电压充电");
}
}
public class IPad implements DigitalProducts{
public void charge() {
System.out.println("平板使用6v电压充电");
}
}
public class MacBook implements DigitalProducts{
public void charge() {
System.out.println("笔记本电脑使用14v电压充电");
}
}
我现在要加入一个小灯泡,但是它们实现的方法不一样:
代码语言:javascript复制public class SmallBulb {
public void electrify() {
System.out.println("使用1V电压给小灯泡通电");
}
}
这时候就需要加上一个适配器类了:
代码语言:javascript复制/**
* 适配器类
*/
public class Adapter implements DigitalProducts {
private SmallBulb smallBulb = new SmallBulb();
public void charge() {
smallBulb.electrify();
}
}
这样在客户端上就可以使用统一的接口了:
代码语言:javascript复制package org.zero01.client;
public class Client {
public static void main(String[] args) {
DigitalProducts phone = new Phone();
DigitalProducts ipad = new IPad();
DigitalProducts macBook = new MacBook();
DigitalProducts samllBulb = new Adapter();
phone.charge();
ipad.charge();
macBook.charge();
samllBulb.charge();
}
}
运行结果:
代码语言:javascript复制手机使用4v电压充电
平板使用6v电压充电
笔记本电脑使用14v电压充电
使用1V电压给小灯泡通电