在生活中,如果我们在香港买了三脚插头回到内地来用,你会发现根据没有插座可以插。因为内地用的电气标准与香港的不一样。那么我们该怎么办?这时我们可以买一个适配器。这样一来香港买的三脚插头就可以插在这一个适配器上,适配器的另一头可以插在内地使用的普通插座上,这样就可以使用了。

从上面这个生活例子,我们可以看出适配器改变了三脚插头,使其符合内地的使用需求。

在面向对象的适配器也是一样的:将一个接口(被适配者接口)转换成另一个接口(目标接口)。

那么这个转换应该怎么做呢?
1.创建一个适配器实现目标接口。
2.在适配器中要取得被适配者的对象引用。
3.实现目标接口的所有方法,每个方法中分别调用相应的被适配者对象的方法就可以完成适配了。

适配器模式中的角色:
1.客户:它是依据目标接口实现的(注意:它的意思不是说客户实现了目标接口,而是需要持有这个接口,引用相应的实例)
2.适配器:它实现了客户依赖的目标接口,并且持有被适配者的实例。
3.被适配者:它实现了被适配者接口。

客户是如何使用我们的适配器的呢?过程如下:
1.客户通过目标接口调用适配器(因为适配器实现了目标接口)的方法,对适配器发出请求。
2.适配器使用被适配者接口,把请求转换成被适配者的一个或多个调用接口。
3.客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。

实现一个适配器所需要进行的工作和目标接口的大小成正比。如果不用适配器,你就必须改客户端的代码来调用这个新的接口,这将会花许多力气来做大量的调整工作和代码改写工作。相比之下,提供一个适配器类,将所有的改变封装在一个类中,是比较好的做法。

我们还可以将一个适配器做成是双向的,支持两边的接口。具体做法就是实现两边的接口。

适配器模式定义

将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。

类图:

设计模式——适配器模式_适配器模式


这个适配器模式有着良好的设计原则:使用对象组合,以修改的接口包装被适配者:这种做法的优点是被适配者的任何子类都可以搭配着适配器一起使用。

这个模式是将客户与接口绑定起来,而不是和实现绑定起来。我们甚至可以使用多个适配器,每一个负责转换不同组的后台类。

我们这里的适配器模式是对象适配器,它利用组合的方式将请求传送给被适配者。其实还有类适配器,它是通过继承目标类和被适配者类的方式来完成,但是Java只允许单继承的。

我觉得适配器模式还可以这样实现:适配器实现目标接口的同时,也继承被适配者类,这样就不用再传被适配者引用到适配器中去了。但是这种实现就不够灵活,只能适配一种被适配者类。

我们用一个实例来说明适配者模式:

1.首先是客户依赖目标接口的代码如下:

package client;

import inter.Target;

public class Client {

private Target target;

public Client(Target target){
this.target = target;
}

public void whatIsTitle(){
System.out.println(target.getTitle());
}

public void whoIsAuthor(){
System.out.println(target.getAuthor());
}

public void WhenIsDistributed(){

System.out.println(target.getDate());
}
}

客户类Client依赖了Target接口。也就是说Client只会处理实现了Target接口的实例。下面是Client依赖的接口:

package inter;

public interface Target {
String getDate(); //日期
String getTitle();//标题
String getAuthor();//作者

}

假设我们从网络获取的数据是符合如下这个接口的:
接口部分:

package inter;

public interface Article {
String getAuthorName();
String getArticleTitle();
String getDistributeDate();
}

实现接口的部分:

package impl;

import inter.Article;

public class ArticleImpl implements Article {
private String authorName;
private String articleTitle;
private String distributeDate;

@Override
public String getAuthorName() {
return authorName;
}

@Override
public String getArticleTitle() {
return articleTitle;
}

@Override
public String getDistributeDate() {
return distributeDate;
}

public void setDistributeDate(String distributeDate) {
this.distributeDate = distributeDate;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}

public void setArticleTitle(String articleTitle) {
this.articleTitle = articleTitle;
}
}

如果我们要想让Client来处理ArticleImpl实例,那是行不通的,因为实现的接口不一样,那么我们就来用适配器做一下接口转换:

package adapter;

import inter.Article;
import inter.Target;

public class Adapter implements Target {

private Article article;
public Adapter(Article article){
this.article = article;
}

@Override
public String getDate() {
return article.getDistributeDate();
}

@Override
public String getTitle() {
return article.getArticleTitle();
}

@Override
public String getAuthor() {
return article.getAuthorName();
}
}

测试一下:

import adapter.Adapter;
import client.Client;
import impl.ArticleImpl;
import inter.Article;

public class Main {

public static void main(String[] args) {
Main main = new Main();
Article article = main.getFromNetwork();

Adapter adapter = new Adapter(article);

Client client = new Client(adapter);

client.whoIsAuthor();
client.whatIsTitle();
client.WhenIsDistributed();


}


public Article getFromNetwork(){
Article article = new ArticleImpl();
((ArticleImpl) article).setAuthorName("Wongkyunban");
((ArticleImpl) article).setArticleTitle("Tomorrow is a day");
((ArticleImpl) article).setDistributeDate("2019-03-26");
return article;
}
}

测试结果:

Wongkyunban
Tomorrow is a day
2019-03-26

我们成功使用适配器模式将接口转换成了Client类所需要的接口。

​Demo代码已共享在Github,欢迎下载学习​​​。
谢谢阅读。