一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
🎉讲解完前 7 种创建型模式,从本文开始,跨入到结构型模式的新篇章!
GoF 23 种设计模式中包含了 7 种结构型的设计模式,结构型模式又可以分为 类结构型模式
和 对象结构型模式
:
- 类结构型模式关心类的组合,一般只存在继承关系和实现关系。
- 对象结构型模式关系类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。
⭐根据”合成复用原则“,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。
OK,话不多说,让我们进入今天的正题吧!
模式动机
现实生活中处处运用了适配器模式,比如我们每天都使用的手机、电脑、平板等各类电子设备的充电器 (电源适配器)。💻笔记本电脑的充电电压(Target
)一般在 20V 左右(视具体型号而定);但是家庭用电电压(Adaptee
)为 220V,二者是不相兼容的,此时充电器(Adapter
)就起到了适配的作用:它能将 220V 交流电转换为设备所能承受电压大小的直流电。
各类转接头也是如此,这就是现实生活中的适配器。
🚧在软件系统中也存在已有接口与客户期望接口不兼容的情况,可能是现有接口名与目标接口名不一致所导致的,又或者是参数类型不匹配...
一旦出现这种情况,我们得考虑如何转换目标接口,保证对现有接口的重用,否则客户就不能利用现有类提供的功能,造成代码冗余。
比如:客户期望调用
quickSort(int[] data)
,但系统中仅存在quickSort(int[] data, int start, int end)
,此时因为参数类型不匹配,所以需要在前者方法中调用后者方法,但传入的参数需要进行稍微修改。简单来说,这是一种重载,也是一种广义上的适配机制。
public static void quickSort(int[] data) {
quickSort(data, 0, data.length - 1);
}
private static void quickSort(int[] data, int start, int end) {
// 具体实现快排...
}
🚀所以我们就要运用类似于电源适配器的设计技巧到我们的编码风格中,在适配器模式中定义一个包装类,包装不兼容接口对象,于是客户类(Target
)的请求就能转换为对适配者类的请求,这个包装类指的就是适配器(Adapter
),它所包装的对象就是适配者(Adaptee
)—— 被适配的类。
整个过程对于客户类是透明的,客户类并不直接访问适配者类,然后却能使得因为接口不兼容而不能交互的类可以一起工作,这就是适配器模式的模式动机。
定义
适配器模式又称封装器(Wrapper)模式,属于结构型模式
。
结构型模式又可以分为
类结构型模式
和对象结构型模式
:
- 类结构型模式关心类的组合,一般只存在继承关系和实现关系。
- 对象结构型模式关系类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。
⭐根据”合成复用原则“,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。
适配器模式能将一个接口包装成客户希望使用的另一个接口,适配器模式使得接口不兼容的类能一起工作;适配器模式既能作为类结构型模式,也可以作为对象结构型模式。
UML 类图
模式结构
适配器模式包含如下角色:
Target
:目标抽象类是客户所调用的接口类Adapter
:适配器类将客户接口的调用转换为对适配者类接口的调用Adaptee
:适配者类提供的接口不兼容,无法被直接调用Client
:客户类只需通过接口与适配器交互即可,将目标抽象类与适配者类解耦
类适配器
对象适配器
更多实例
算法适配器
假设存在一个接口 DataOperation
定义了排序方法 sort(int[])
和查找方法 search(int[], int)
,已知在系统中已经存在 QuickSort
类的 quickSort(int[])
方法实现了快速排序,BinarySearch
类的 binarySearch(int[], int)
方法实现了二分查找,现使用适配器模式设计该系统,使其在不修改源码的情况下,适配(复用)已存在的接口到 DataOperation
接口。
示例代码
类适配器
Target.java
public interface Target {
void request();
}
Adaptee.java
public class Adaptee {
public void specificRequest() {
// ...
}
}
Adapter.java
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
// TODO: You can do something here
specificRequest();
}
}
Client.java
public class Client {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
对象适配器
Target.java
public abstract class Target {
abstract void request();
}
Adaptee.java
public class Adaptee {
public void specificRequest() {
// ...
}
}
Adapter.java
public class Adapter extends Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
void request() {
// TODO: You can do something here
adaptee.specificRequest();
}
}
Client.java
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request();
}
}
优缺点
✔将目标类与适配者类解耦,通过引入一个适配器类来重用现有适配者类,而无须修改原有代码。
✔增加了类的透明性和复用性,将具体实现代码封装在适配者类中,对于客户而言是透明的,同时提高了适配者的复用性;适配者类中的代码还有可能是第三方接口,使用适配器极大地提高复用性!完美符合“单一职责原则”。
✔灵活性和扩展性较好,通过配置文件,可以灵活更换适配器,也可以在不修改原有代码的基础上新增适配器列,符合“开闭原则”。
⭐类适配器
额外优缺点:
✔由于适配器类(Adapter)是适配者类(Adaptee)的子类,因此可以在适配器类中置换一些适配者方法,使得适配器的灵活性更强。
❌对于 Java
、C#
等不支持多继承特性的语言,一次最多只能适配一个适配者类(Adaptee);而且目标类只能为接口,不能像对象适配器的目标类一样可为接口也可以为具体类,具有一定的局限性。
⭐对象适配器
额外优缺点:
✔一个对象适配器可以把多个不同的适配者都适配到同一个目标类。
✔可以适配一个适配者的子类,由于适配器与适配者之间是关联关系,根据“里氏替换原则”,适配者的子类也可以通过该适配器进行适配。
❌与类适配器模式相比,要想置换(增强)适配者类的方法就不容易。如果非要置换适配者类的方法,可以先用一个子类继承适配者类,然后覆写方法,再将该子类作为真正的适配器进行适配。
适用场景
在以下情况推荐使用适配器模式:
(1)系统需要复用现有类,但其接口不符合系统需要。
(2)想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
「适配器模式」落地
JDBC 驱动
JDBC
提供一个客户端通用的抽象接口,每一个具体数据库引擎(如 MySQL、Oracle、SQL Server...)的 JDBC 驱动软件(Adapter)都是一个介于 JDBC 接口(Target)和数据库引擎接口(Adaptee)之间的适配器软件。
Spring AOP
中的适配器模式
在 Spring AOP 框架中,使用 Advice
通知来增强被代理类的功能,常见 Advice
类型有
MethodBeforeAdvice
AfterReturningAdvice
ThrowsAdvice
每个类型 Advice
都有其对应的拦截器
MethodBeforeAdviceInterceptor
AfterReturningAdviceInterceptor
ThrowsAdviceInterceptor
Spring 则需要将每个 Advice
都封装成对应的拦截器类型,然后不同类型的 Interceptor
通过适配器统一对外提供接口,如下类图:
Client
-->Target
-->Adapter
-->Interceptor
-->Advice
⭐我们来看看客户端 DefaultAdvisorAdapterRegistry
是如何创建拦截器:在 for 循环中,逐个取出注册的适配器,调用 supportsAdvice()
判断 Advice
对应的类型,然后调用 getInterceptor()
创建对应类型的拦截器。
@SuppressWarnings("serial")
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
private final List<AdvisorAdapter> adapters = new ArrayList<>(3);
/**
* Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters.
*/
public DefaultAdvisorAdapterRegistry() {
// 注册适配器
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) {
// 调用适配器方法 supportsAdvice()
if (adapter.supportsAdvice(advice)) {
// 调用适配器方法 getInterceptor()
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
// ...
}
最终 Spring 将不同的 Advice
封装成 Interceptors Chain
以实现被代理类的增强。
Spring MVC
中的适配器模式
Spring MVC 中的适配器模式主要用于执行 Controller
中的请求处理方法。
如下简单阐述 Spring MVC 各对象在适配器模式中的角色。
Spring MVC 中,DispatcherServlet
作为用户 Client,HandlerAdapter
作为期望接口 Target,具体的适配器实现类作为 Adapter 对目标适配者类进行适配,Controller
作为适配者 Adaptee。
为什么要在 Spring MVC 中使用适配者模式?
Spring MVC 中的 Controller
种类众多,不同类型的 Controller
通过不同方法来对请求进行处理。如果不利用适配者模式的话,那么 DispatcherServlet
需要直接获取对应的 Controller
,然后自行判断属于哪种 Controller
类型,紧接着进行对应处理,那么就会产生大量的 if-else
语句:
// 代码坏味道
if (mappedHandler.getHandler() instanceof MultiActionController) {
((MultiActionController) mappedHandler.getHandler()).xxx
} else if (mappedHandler.getHandler() instanceof UrlFilenameViewController) {
((UrlFilenameViewController) mappedHandler.getHandler()).xxx
} else if (...) {
...
}
这样假设我们再新增一个 HardController
,就要在代码中添加 if(mappedHandler.getHandler() instanceof HardController)
,这种形式违背了”开闭原则“——对扩展开放,对修改关闭,使得程序难以维护。
⭐详细剖析下 Spring MVC 源码中是如何使用适配器模式的!
我们先来看看源码,首先是适配器 (需要实现的) 接口 HandlerAdapter
:
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
现该接口的适配器每一个 Controller
都有一个适配器与之对应,这样一旦每定义一个 Controller
就需要定义一个实现 HandlerAdapter
的适配器。
Spring MVC 中提供的 Controller
的实现类如下:
Spring MVC 中提供的 HandlerAdapter
的实现类如下:
我们可以来看看其中具体的某一个适配器类代码,如 HttpRequestHandlerAdapter
:
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
当 Spring 容器启动后,会将所有定义好的适配器对象存放在一个 List
集合中,当一个请求来临时,DispatcherServlet
会通过 handler
的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 handler()
方法来调用 Controller
中的用于处理请求的方法。
节选关键源码
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
/** List of HandlerAdapters used by this servlet */
@Nullable
private List<HandlerAdapter> handlerAdapters;
// 容器初始化策略
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
// 初始化 handlerAdapters
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
// 遍历所有的 HandlerAdapters, 通过 supports() 找到匹配的适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
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");
}
// 分发请求,请求需要找到匹配的适配器来处理
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 确定当前请求匹配的适配器!
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
// 省略其他源码
}
Spring MVC 通过适配器模式将所有的 Controller
统一交给 HandlerAdapter
处理,免去了大量的 if-else
语句对 Controller
进行判断,也更利于扩展新的 Controller
类型。
InputStreamAdapter
适配器
浅显易懂的对象适配器,不做过多赘述
模式扩展
默认适配器
其实除了类适配器与对象适配器,还存在一种默认适配器,又称缺省适配器模式。
当不需要实现 Target 接口提供的所有方法时,可先设计一个抽象类实现接口,并为接口中的每个方法提供默认方法(空方法),那么该抽象类的子类就可以有选择的覆盖父类中的某些方法来实现需求。
它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式。
如下就是一个典型的例子
public abstract class WindowAdapter
implements WindowListener, WindowStateListener, WindowFocusListener
{
public void windowOpened(WindowEvent e) {}
public void windowClosing(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowStateChanged(WindowEvent e) {}
public void windowGainedFocus(WindowEvent e) {}
public void windowLostFocus(WindowEvent e) {}
}
该例中,具体的适配器只需要继承 WindowAdapter
并有选择地覆盖其中的方法即可。
双向适配器
在对象适配器的使用过程中,如果在适配器中同时包含对 Target 目标类和 Adaptee 适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器。
最后
👇下一篇:「设计模式」🌉桥接模式(Bridge)
❤️ 好的代码无需解释,关注「手撕设计模式」专栏,跟我一起学习设计模式,你的代码也能像诗一样优雅!
❤️ / END / 如果本文对你有帮助,点个「赞」支持下吧,你的支持就是我最大的动力!