真实业务场景了解适配器模式


theme: channing-cyan

阅读本文大概需要 10 分钟

本文目录

  1. 引言
  2. 适用场景
  3. 简单例子编码
  4. 业务场景举例
  5. 框架运用举例

引言

小明和小红从中国出发到英国玩,到了酒店的房间

小红 🥺:我手机怎么没电了,我要充电,不对啊,小明你看外国的插座和我们中国的咋不一样,我的充电器只有两个头,为什么这个插座是三个头

小明 😎:宝,咋们中国的手机充电器是阴极、阳极,外国多了一个地极,所以我们需要一个 “适配器” 就可以给我们手机充电啦

小红 🤭:那你带了吗?

小明 🤨:没有

~~小明卒,本文结束。~~ 图片名称

例子虽然不咋样,但是简单理解,说明了问题

通俗理解,适配器就是一个中间角色,起到一个协调兼容的作用

在我们软件开发上理解,适配器模式是结构型的设计模式,将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以在一起工作

它在我们开发软件中的优点就是能提高类的透明性和复用度,现有的类复用不需要改变,解决了目标类和现有类不匹配的问题。并且目标类和适配器类解耦,提高程序扩展性

缺点也显而易见,增加系统代码可读的难度,比如调用 a 接口,其实内部已经偷偷调用了 b 接口

适用场景

适配器适用的场景通常为已经存在的类,它的方法和需求不匹配

当然如果是强势的合作方,可以完全以自己的接口为主,但是通常情况是会随着软件维护,由于不同产品,不同厂家造成功能类似而接口不相同,在此种情况下,就需要用到适配器(在下文业务场景中,会具体举例)

简单举例

适配器模式通常分为对象适配和类适配,最大的区别就在于一个是实用类继承来实现,一个是实用组合来实现

我们先来举例 类适配

假设现存一个 音频播放的 MediaPlayer 接口

Java public interface MediaPlayer { void play(); }

旧的播放器 OldMediaPlayer 只支持播放 Mp4 格式的影片

```Java public class OldMediaPlayer implements MediaPlayer { @Override public void play() { System.out.println("播放 Mp4 中"); }

} ```

搞个测试类看一下

```Java public class Test { public static void main(String[] args) { MediaPlayer mediaPlayer = new OldMediaPlayer(); mediaPlayer.play(); } }

// 输出 播放 Mp4 中 ```

ok,正常播放中

现在国家发展,大家过上了小康的生活,想看点 avi 格式的影片

我们先搞一个能播放 avi 的播放器 NewMediaPlayer

Java public class NewMediaPlayer { public void adapteePlay(){ System.out.println("播放 Avi 中"); } }

然后需要一个适配器 Adapter,去兼容 play 这个接口动作,使得我们的新播放器可以和旧的播放器一样使用 play 去播放影片

Java public class Adapter extends NewMediaPlayer implements MediaPlayer { @Override public void play() { super.adapteePlay(); } }

我们继承 NewMediaPlayer 并且实现 MediaPlayer 接口,在 play 方法中,去调用父类的 NewMediaPlayeradapteePlay 方法,完成移花接木。妙啊

单测走起来

```Java public class Test { public static void main(String[] args) { MediaPlayer mediaPlayer = new OldMediaPlayer(); mediaPlayer.play(); // 父亲引用指向子类对象 MediaPlayer adapterMediaPlayer = new Adapter(); adapterMediaPlayer.play(); } }

// 输出 播放 Mp4 中 播放 Avi 中 ```

一切正常,适配器完美发挥作用 😎

冷静思考,这个适配器有没有更好的写法,当然有,就是我们的 对象适配,使用组合去代替继承,并且 《effective java》 和 阿里《码出高效》 都推荐组合优于继承

接下来,针对 Adapter 方法进行改造

```Java public class Adapter implements MediaPlayer {

private NewMediaPlayer newMediaPlayer = new NewMediaPlayer();

@Override
public void play() {
    newMediaPlayer.adapteePlay();
}

} ```

测试类测试发现输出结果一样,工资 + 1

业务场景举例

在消费金融场景下

随着一步一个脚印,做大做强,我们对接了越来越多的信托公司和银行来放款(我们统称为渠道)。不同的渠道,放款后都需要做一些不同的操作,诸如更新还款计划、增加限额等等

所以这⾥可以用适配器来统一维护一下,当然如果你去编写 if 语句句也是可以实现的,~~屎山开始~~

假设现存 A1 银行,B2 银行 通知我们放款成功来举例

Java public class A1LoanService { public void a1Loan(ChannelLoanContext context) { System.out.println("A1 渠道放款成功"); } }

Java public class B2LoanService { public void b2Loan(ChannelLoanContext context) { System.out.println("B2 渠道放款成功"); } }

规定一个统一的放款接口,即渠道执行器 ChannelAdapterExecutor

Java public interface ChannelAdapterExecutor { boolean loan(ChannelLoanContext context); }

A1LoanServiceB2LoanService 外面套一层渠道执行器都统一去执行 loan 动作

```Java public class A1ChannelExecutor implements ChannelAdapterExecutor {

private A1LoanService a1LoanService = new A1LoanService();

@Override
public boolean loan(ChannelLoanContext context) {
    a1LoanService.a1Loan(context);
    return true;
}

} ```

```Java public class B2ChannelExecutor implements ChannelAdapterExecutor {

@Autowired
private B2LoanService b2LoanService = new B2LoanService();

@Override
public boolean loan(ChannelLoanContext context) {
    b2LoanService.b2Loan(context);
    return true;
}

} ```

妙啊,后期还有更多的渠道都是这样去实现的,充分满⾜了单一职责。

写个测试类,看一看成果

```Java public class Test { public static void main(String[] args) {

ChannelLoanContext a1ChannelLoanContext = new ChannelLoanContext();
    a1ChannelLoanContext.setChannelNo("A1");
    a1ChannelLoanContext.setLoanAmount(new BigDecimal("123"));
    ChannelAdapterExecutor a1ChannelAdapterExecutor = new A1ChannelExecutor();
    a1ChannelAdapterExecutor.loan(a1ChannelLoanContext);


    ChannelLoanContext b2ChannelLoanContext = new ChannelLoanContext();
    b2ChannelLoanContext.setChannelNo("A1");
    b2ChannelLoanContext.setLoanAmount(new BigDecimal("1234"));
    ChannelAdapterExecutor b2ChannelAdapterExecutor = new B2ChannelExecutor();
    b2ChannelAdapterExecutor.loan(b2ChannelLoanContext);

}

} // 输出 A1 渠道放款成功 B2 渠道放款成功 ```

ok,测试也通过了,适配器干的漂亮

多说一句,这边的 ChannelLoanContext 在实际业务中,我们都是通过网关或者数据库配置进行统一的字段转换,满足我们标准化的要求。并且根据不同的渠道号,搭配工厂模式,路由到具体的渠道执行器

框架运用举例

SpringMVC 中的 HandlerAdapter,就使用了适配器模式,一听这名字就有内味了

看看 HandlerAdapter 的实现类

我们就知道 SpringMvc 为什么需要 HandlerAdapter

在容器里均在多个处理器,即 Controlle,在上图我们就可以看到,如果采取直接调用 Controller 方法,就不得不 if else,如果后期需要拓展新的处理器,就需要修改原来的代码,违背我们的开闭原则

假设我们这样做了...

Java if(mappedHandler.getHandler() instanceof SimpleController){ ((SimpleController)mappedHandler.getHandler()).xxx }else if(xx){ ... }else if(...){ ... } 我们增加一个 controller,就需要多写一个 if

不是非常优雅 🤨

ok,我们接下来深入了解一下,Spring中是怎么做的

打开 DispatcherServlet 中的 doDispatch方法

不了解 DispatcherServlet 作用的小伙伴可以自己百度一下,当然你可以继续往下看,只需要知道它简单来说就是返回一个 ModeAndView mv 对象

看到 940 行,它是根据具体的servlet请求获取对应的处理器(controller)

Java mappedHandler = getHandler(processedRequest);

获得到 具体的处理器 mappedHandler 之后,在 947行

Java HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

getHandlerAdapter,循环所有的 handlerAdapters,找到合适的 Adapter

Java /** * Return the HandlerAdapter for this handler object. * @param handler the handler object to find an adapter for * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error. */ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }

通过 getHandlerAdapter 获得到具体的 Controller 的适配器后

再到 967 行

Java mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 根据 Adapter 执行对应 controller 里面的方法,返回 ModelAndView

至此为此,我们已经理解到 Spring 是如何处理找到的具体的 Controller了,当然需要扩展的 Controller 的时候,只需要增加一个适配器类就可以了。


文章结束,如果本文对你有所帮助的话,那就点个赞吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值