设计模式之适配器模式

适配器模式

适配器模式

适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作,属于结构型设计模式。

也就是说,当前系统存在两种接口A和B,客户只支持访问A接口,但是当前系统没有A接口对象,但是有B接口对象,但客户无法识别B接口,因此需要通过一个适配器C,将B接口内容转换成A接口,从而使得客户能够从A接口获取得到B接口内容。

在软件开发中,基本上任何问题都可以通过增加一个中间层进行解决。适配器模式其实就是一个中间层。综上,适配器模式其实起着转化/委托的作用,将一种接口转化为另一种符合需求的接口。

适配器模式的应用场景

提供一个转换器(适配器),将当前系统存在的一个对象转化为客户端能够访问的接口对象。

适配器适用于以下几种业务场景:

1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。

2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。

生活中也非常的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。

适配器模式一般包含三种角色:

目标角色(Target):也就是我们期望的接口;

源角色(Adaptee):存在于系统中,内容满足客户需求(需转换),但接口不匹配的接口实例;

适配器(Adapter):将源角色(Adaptee)转化为目标角色(Target)的类实例;

适配器模式各角色之间的关系如下:

假设当前系统中,客户端需要访问的是Target接口,但Target接口没有一个实例符合需求,而Adaptee实例符合需求;但是客户端无法直接使用Adaptee(接口不兼容);因此,我们需要一个适配器(Adapter)来进行中转,让Adaptee能转化为Target接口形式;

适配器模式有3种形式:类适配器、对象适配器、接口适配器。

类适配器

类适配器的原理就是通过继承来实现适配器功能。具体做法:让Adapter实现Target接口,并且继承Adaptee,这样Adapter就具备Target和Adaptee的特性,就可以将两者进行转化。

下面来看UML类图:

image-20210420231952405

源角色(Adaptee)

1
2
3
4
5
6
7
public class Adaptee
{
public String specificRequest(String str)
{
return str + " from Adaptee";
}
}

目标角色(Target)

1
2
3
4
public interface Target
{
String request(String str);
}

适配器(Adapter)

1
2
3
4
5
6
7
8
9
public class Adapter extends Adaptee implements Target
{
@Override
public String request(String str)
{
String res = super.specificRequest(str);
return res + " from Adapter";
}
}

测试代码

1
2
3
4
5
6
7
8
9
public class Main
{
public static void main(String[] args)
{
Adapter adapter = new Adapter();
String res = adapter.request("request");
System.out.println(res); // 输出:request from Adaptee from Adapter
}
}

对象适配器

对象适配器的原理就是通过组合来实现适配器功能。具体做法:让Adapter实现Target接口,然后内部持有Adaptee实例,然后再Target接口规定的方法内转换Adaptee。

image-20210420232802757

适配器(Adapter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Adapter implements Target
{
private Adaptee adaptee;

public Adapter(Adaptee adaptee)
{
this.adaptee = adaptee;
}

@Override
public String request(String str)
{
String res = adaptee.specificRequest(str);
return res + " from Adapter";
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
public class Main
{
public static void main(String[] args)
{
Adaptee adaptee = new Adaptee();
Adapter adapter = new Adapter(adaptee);
String res = adapter.request("request");
System.out.println(res); // 输出:request from Adaptee from Adapter
}
}

接口适配器

接口适配器的关注点与类适配器和对象适配器的关注点不太一样,类适配器和对象适配器着重于将系统存在的一个角色(Adaptee)转化成目标接口(Target)所需内容,而接口适配器的使用场景是解决接口方法过多,如果直接实现接口,那么类会多出许多空实现的方法,类显得很臃肿,此时,使用接口适配器就能让我们只实现我们需要的接口方法,目标更清晰。

image-20210420233903984

目标角色(Target)

1
2
3
4
5
6
public interface Target
{
String request1(String str);
String request2(String str);
String request3(String str);
}

适配器(Adapter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Adapter implements Target
{
private Adaptee adaptee;

public Adapter(Adaptee adaptee)
{
this.adaptee = adaptee;
}

@Override
public String request1(String str)
{
String res = adaptee.specificRequest(str);
return res + " from Adapter";
}

@Override
public String request2(String str)
{
return "";
}

@Override
public String request3(String str)
{
return "";
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
public class Main
{
public static void main(String[] args)
{
Adaptee adaptee = new Adaptee();
Adapter adapter = new Adapter(adaptee);
String res = adapter.request1("req1");
System.out.println(res); // 输出:req1 from Adaptee from Adapter
}
}

适配器模式在源码中的体现

Spring中适配器模式也应用得非常广泛,例如:SpringAOP中的AdvisorAdapter类,它有三个实现类MethodBeforeAdviceAdapter、 AfterReturningAdviceAdapter和ThrowsAdviceAdapter,先来看顶层接口AdvisorAdapter的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface AdvisorAdapter {

/**
* Does this adapter understand this advice object? Is it valid to
* invoke the {@code getInterceptors} method with an Advisor that
* contains this advice as an argument?
* @param advice an Advice such as a BeforeAdvice
* @return whether this adapter understands the given advice object
* @see #getInterceptor(org.springframework.aop.Advisor)
* @see org.springframework.aop.BeforeAdvice
*/
boolean supportsAdvice(Advice advice);

/**
* Return an AOP Alliance MethodInterceptor exposing the behavior of
* the given advice to an interception-based AOP framework.
* <p>Don't worry about any Pointcut contained in the Advisor;
* the AOP framework will take care of checking the pointcut.
* @param advisor the Advisor. The supportsAdvice() method must have
* returned true on this object
* @return an AOP Alliance interceptor for this Advisor. There's
* no need to cache instances for efficiency, as the AOP framework
* caches advice chains.
*/
MethodInterceptor getInterceptor(Advisor advisor);

}

MethodBeforeAdviceAdapter 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}

@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}

}

Spring会根据不同的AOP配置来确定使用对应的Advice,跟策略模式不同的是一个方法可以同时拥有多个Advice。

SpringMVC中的HandlerAdapter类也是适配器模式,类图如下

image-20210420235541169

适配器模式和装饰器模式对比

装饰器和适配器模式都是包装模式(Wrapper Pattern),装饰器也是一种特殊的代理模式。

装饰器模式 适配器模式
形式 是一种非常特别的适配器模式 没有层级关系,装饰器模式有层级关系
定义 装饰器和被装饰器都实现同个接口,主要目的是为了扩展之后依旧保留OOP关系 适配器和被适配者没有必然的联系,采用继承或代理的形式进行包装
关系 满足is-a的关系 满足has-a的关系
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑

适配器模式的优缺点

优点:

1、能提高类的透明性和复用,现有的类复用但不需要改变。

2、目标类和适配器类解耦,提高程序的扩展性。

3、在很多业务场景中符合开闭原则。

缺点:

1、适配器编写过程需要全面考虑,可能会增加系统的复杂性。

2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

打赏

请我喝杯咖啡吧~

支付宝
微信