11.适配器模式
11.1.定义
适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
示意图
生活场景:电源插转换头、手机充电转换头、显示器转接头。
你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程藏于幕后。 被封装的对象甚至察觉不到适配器的存在。
适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时你甚至可以创建一个双向适配器来实现双向转换调用。
11.2.应用场景
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
- 适配不同格式的数据
11.3.适配器角色
Adaptee 是一组不兼容 ITarget 接口定义的接口。
ITarget 表示要转化成的接口定义。
Adapter/Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口。
适配器有3中形式:类适配器、对象适配器、接口适配器
11.4.类适配器
类适配器: 基于继承。
做法:让Adaptor实现ITarget接口,并且继承Adaptee,这样Adaptor就具备ITarget和Adaptee的特性,就可以将两者进行转化。
UML类图:
// 类适配器: 基于继承 // ITarget 表示要转化成的接口定义 public interface ITarget { void f1(); void f2(); void fc(); } // Adaptee 是一组不兼容 ITarget 接口定义的接口 public class Adaptee { public void fa() { //... } public void fb() { //... } public void fc() { //... } } // Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口 public class Adaptor extends Adaptee implements ITarget { public void f1() { super.fa(); } public void f2() { //...重新实现f2()... } // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点 }
11.5.对象适配器
对象适配器:基于组合。
做法:让Adaptor 实现ITarget 接口,然后内部持有Adaptee实例,然后在ITarget接口规定的方法内转换Adaptee。
URL类图:
// 对象适配器:基于组合 // ITarget 表示要转化成的接口定义 public interface ITarget { void f1(); void f2(); void fc(); } // Adaptee 是一组不兼容 ITarget 接口定义的接口 public class Adaptee { public void fa() { //... } public void fb() { //... } public void fc() { //... } } // Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口 public class Adaptor implements ITarget { private Adaptee adaptee; public Adaptor(Adaptee adaptee) { this.adaptee = adaptee; } public void f1() { adaptee.fa(); //委托给Adaptee } public void f2() { //...重新实现f2()... } public void fc() { adaptee.fc(); } }
11.5.接口适配器
使用接口适配器让我们只实现我们所需要的接口方法
public class CD { //这个类来自外部sdk,我们无权修改它的代码 //... public static void staticFunction1() { //... } public void uglyNamingFunction2() { //... } public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... } public void lowPerformanceFunction4() { //... } } // 使用适配器模式进行重构 public class ITarget { void function1(); void function2(); void fucntion3(ParamsWrapperDefinition paramsWrapper); void function4(); //... } // 注意:适配器类的命名不一定非得末尾带Adaptor public class CDAdaptor extends CD implements ITarget { //... public void function1() { super.staticFunction1(); } public void function2() { super.uglyNamingFucntion2(); } public void function3(ParamsWrapperDefinition paramsWrapper) { super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...); } public void function4() { //...reimplement it... } }
11.6.实战
重构第三方登录自由适配场景
首先创建统一登陆结果ResultMsg类:
@Data public class ResultMsg { private int code; private String msg; private Object data; public ResultMsg(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } }
假设老系统的的登录逻辑PasswordService:
public class PassportService { /** * 注册方法 * @param username * @param password * @return */ public ResultMsg regist(String username,String password){ return new ResultMsg(200,"注册成功",new Member()); } /** * 登录的方法 * @param username * @param password * @return */ public ResultMsg login(String username,String password){ return null; } }
遵循开闭原则,老代码我们不修改。开启代码重构之路,创建Member类:
@Data public class Member { private String username; private String password; private String mid; private String info; }
运行稳定的代码不改动。创建ITarget角色IPassportForThird接口。
public interface IPassportForThird { ResultMsg loginForQQ(String openId); ResultMsg loginForWechat(String openId); ResultMsg loginForToken(String token); ResultMsg loginForTelphone(String phone, String code); }
创建适配器兼容Adaptor角色PassportForThirdAdapter类
public class PassportForThirdAdapter implements IPassportForThird { public ResultMsg loginForQQ(String openId) { return processLogin(openId, LoginForQQAdapter.class); } public ResultMsg loginForWechat(String openId) { return processLogin(openId, LoginForWechatAdapter.class); } public ResultMsg loginForToken(String token) { return processLogin(token, LoginForTokenAdapter.class); } public ResultMsg loginForTelphone(String phone, String code) { return processLogin(phone, LoginForTelAdapter.class); } private ResultMsg processLogin(String id,Class extends ILoginAdapter> clazz){ try { ILoginAdapter adapter = clazz.newInstance(); if (adapter.support(adapter)){ return adapter.login(id,adapter); } } catch (Exception e) { e.printStackTrace(); } return null; } }
根据不同登录方式,创建不同登录Adaptor。首先,创建LoginAdapter接口:
public interface ILoginAdapter { boolean support(Object object); ResultMsg login(String id,Object adapter);}
创建一个抽象类AbstraceAdapter继承PassportService原有功能,同时实现ILoginAdapter接口,然后分别实现不同的登录适配。
QQ登录
public class LoginForQQAdapter extends AbstraceAdapter{ public boolean support(Object adapter) { return adapter instanceof LoginForQQAdapter; } public ResultMsg login(String id, Object adapter) { if(!support(adapter)){return null;} //accesseToken //time return super.loginForRegist(id,null); }}
微信登录
public class LoginForWechatAdapter extends AbstraceAdapter{ public boolean support(Object adapter) { return adapter instanceof LoginForWechatAdapter; } public ResultMsg login(String id, Object adapter) { return super.loginForRegist(id,null); }}
手机登录
public class LoginForTelAdapter extends AbstraceAdapter{ public boolean support(Object adapter) { return adapter instanceof LoginForTelAdapter; } public ResultMsg login(String id, Object adapter) { return super.loginForRegist(id,null); }}
Token登录
public class LoginForTokenAdapter extends AbstraceAdapter { public boolean support(Object adapter) { return adapter instanceof LoginForTokenAdapter; } public ResultMsg login(String id, Object adapter) { return super.loginForRegist(id,null); }}
创建适配器PassportForThirdAdapter类,实现目标接口IPassportForThird兼容。
public class PassportForThirdAdapter implements IPassportForThird { public ResultMsg loginForQQ(String openId) { return processLogin(openId, LoginForQQAdapter.class); } public ResultMsg loginForWechat(String openId) { return processLogin(openId, LoginForWechatAdapter.class); } public ResultMsg loginForToken(String token) { return processLogin(token, LoginForTokenAdapter.class); } public ResultMsg loginForTelphone(String phone, String code) { return processLogin(phone, LoginForTelAdapter.class); } private ResultMsg processLogin(String id,Class extends ILoginAdapter> clazz){ try { ILoginAdapter adapter = clazz.newInstance(); if (adapter.support(adapter)){ return adapter.login(id,adapter); } } catch (Exception e) { e.printStackTrace(); } return null; }}
客户端测试代码
public class Test { public static void main(String[] args) { IPassportForThird adapter = new PassportForThirdAdapter(); adapter.loginForQQ("sdfasdfasfasfas"); }}
类图
我们为每一个适配器加上了support()方法,用来判断是否兼容,参数是Object,来源于接口。ILoginAdapter接口是为了代码规范。上面的代码综合了策略模式、简单工厂和适配器模式。
11.7.源码
Spring AOP中的AdvisorAdapter。有三个实现类MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter。
顶层接口AdvisorAdapter类
public interface AdvisorAdapter { boolean supportsAdvice(Advice var1); MethodInterceptor getInterceptor(Advisor var1);}
MethodBeforeAdviceAdapter实现。其余两个不贴了。
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable { AfterReturningAdviceAdapter() { } public boolean supportsAdvice(Advice advice) { return advice instanceof AfterReturningAdvice; } public MethodInterceptor getInterceptor(Advisor advisor) { AfterReturningAdvice advice = (AfterReturningAdvice)advisor.getAdvice(); return new AfterReturningAdviceInterceptor(advice); }}
Spring 根据不同的AOP配置确定使用相应的Advice。
下面看SpringMVC的HandlerAdapter类,它也有多个子类。
类图:
他的适配关键代码在DispatcherServlet的doDispatch()方法中,我们看源码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (this.logger.isDebugEnabled()) { this.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; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } }}
doDispatch方法调用了getHandlerAdaper方法
@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { Iterator var2 = this.handlerMappings.iterator(); while(var2.hasNext()) { HandlerMapping hm = (HandlerMapping)var2.next(); if (this.logger.isTraceEnabled()) { this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } } return null;}
getHandlerAdapter方法循环调用supports方法判断是否兼容,循环迭代集合中的Adapter是在初始化时早就赋了值的。只有源码专题继续讲解。
11.8.适配器与装饰器的对比
两者都是包装模式
适配器装饰器形式适配器没有层级关系,装饰器有层级关系。特殊的适配器定义适配器与被适配者没有必然联系,通常通过继承或代理进行包装装饰器与被装饰者实现同一个接口,目的是为了扩展后保留OOP关系关系has-a的关系is-a的关系功能注重兼容、转换注重覆盖、扩展设计后置考虑前置考虑
11.9.适配器模式的优缺点
优点:
- 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离。
- 开闭原则。只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
缺点:
- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
11.10.总结
一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”,如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。那在实际的开发中,什么情况下才会出现接口不兼容呢?我总结下了下面这样 5 种场景:
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
- 适配不同格式的数据
12.桥接模式
先复习代理模式。它在不改变原始类(或者叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。代理模式在平时的开发经常被用到,常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。
今天学习桥接模式。桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,所以,相当于代理模式来说,桥接模式在实际的项目中并没有那么常用,你只需要简单了解,见到能认识就可以,并不是我们学习的重点。
12.1.原理解析
桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。这个模式可以说是 23 种设计模式中最难理解的模式之一了。我查阅了比较多的书籍和资料之后发现,对于这个模式有两种不同的理解方式。
示意图
当然,这其中“最纯正”的理解方式,当属 GoF 的《设计模式》一书中对桥接模式的定义。毕竟,这 23 种经典的设计模式,最初就是由这本书总结出来的。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”
关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则,所以,这里我就不多解释了。我们重点看下 GoF 的理解方式。
桥接模式通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
12.2.角色
类图:
桥接模式有四个角色:
抽象(Abstraction):该类持有一个对实现角色的引用,抽象角色中的方法需要实现角色。该类一般为抽象类。
修正抽象角色(RefinedAbstraction):Abstraction的具体实现,对Abstraction方法进行完善与扩展。
抽象实现角色(Implementor):确定实现维度的基本操作,提供给Abstraction使用。该类一般为接口或抽象类。
具体实现角色(ConcreteImplementor):Implementor的具体实现。
12.3.通用写法
创建抽象角色
// 抽象public abstract class Abstraction { protected IImplementor mImplementor; public Abstraction(IImplementor implementor) { this.mImplementor = implementor; } public void operation() { this.mImplementor.operationImpl(); }}
修正抽象角色
// 修正抽象public class RefinedAbstraction extends Abstraction { public RefinedAbstraction(IImplementor implementor) { super(implementor); } @Override public void operation() { super.operation(); System.out.println("refined operation"); }}
抽象实现角色
// 抽象实现public interface IImplementor { void operationImpl();}
具体实现角色
// 具体实现public class ConcreteImplementorA implements IImplementor { public void operationImpl() { System.out.println("I'm ConcreteImplementor A"); }}
测试代码
public class Test { public static void main(String[] args) { // 来一个实现化角色 IImplementor imp = new ConcreteImplementorA(); // 来一个抽象化角色,聚合实现 Abstraction abs = new RefinedAbstraction(imp); // 执行操作 abs.operation(); }}
运行结果
I'm ConcreteImplementor Arefined operation
12.4.应用场景
- 拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类),可以使用桥接模式。
- 如果你希望在几个独立维度上扩展一个类,可使用该模式。
- 如果你需要在运行时切换不同实现方法,可使用桥接模式。
12.5.业务场景中的运用
办公发送邮件、短信消息或者系统消息。紧急程度分为普通消息、紧急消息、特急消息。
使用继承的话,情况复杂,也不利于扩展。通过桥接来解决。
创建一个IMessage接口担任桥接角色:
public interface IMessage { //发送消息的内容和接收人 void send(String message,String toUser);}
创建邮件消息实现EmailMessage类:
public class EmailMessage implements IMessage { public void send(String message, String toUser) { System.out.println("使用邮件消息发送" + message + "给" + toUser); }}
创建手机消息实现SmsMessage类:
public class SmsMessage implements IMessage { public void send(String message, String toUser) { System.out.println("使用短信消息发送" + message + "给" + toUser); }}
创建桥接抽象角色AbastractMessage类
public abstract class AbastractMessage { private IMessage message; public AbastractMessage(IMessage message) { this.message = message; } void sendMessage(String message,String toUser){ this.message.send(message,toUser); }}
创建具体实现普通消息类NormalMessage:
public class NomalMessage extends AbastractMessage { public NomalMessage(IMessage message) { super(message); }}
创建短信消息类SmsMessage:
public class SmsMessage implements IMessage { public void send(String message, String toUser) { System.out.println("使用短信消息发送" + message + "给" + toUser); }}
创建紧急消息UrgencyMessage类:
public class UrgencyMessage extends AbastractMessage { public UrgencyMessage(IMessage message) { super(message); } void sendMessage(String message, String toUser){ message = "【加急】" + message; super.sendMessage(message,toUser); } public Object watch(String messageId){ return null; }}
代码测试
public class Test { public static void main(String[] args) { IMessage message = new SmsMessage(); AbastractMessage abastractMessage = new NomalMessage(message); abastractMessage.sendMessage("加班申请","王总"); message = new EmailMessage(); abastractMessage = new UrgencyMessage(message); abastractMessage.sendMessage("加班申请","王总"); }}
运行效果:
使用短信消息发送加班申请给王总使用邮件消息发送【加急】加班申请给王总
12.6.源码
JDBC API,其中Driver类就是桥接对象。使用Class.forName方法动态加载各个数据库厂商的Driver类。
以MySQL的实现为例:
//1.加载驱动Class.forName("com.mysql.jdbc.Driver"); //反射机制加载驱动类// 2.获取连接Connection//主机:端口号/数据库名Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");// 3.得到执行sql语句的对象StatementStatement stmt = conn.createStatement();// 4.执行sql语句,并返回结果
我们看一下Driver接口定义:
public interface Driver { Connection connect(String url, java.util.Properties info) throws SQLException; boolean acceptsURL(String url) throws SQLException; DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException; int getMajorVersion(); int getMinorVersion(); boolean jdbcCompliant(); public Logger getParentLogger() throws SQLFeatureNotSupportedException;}
Driver在JDBC中没有做任何实现,具体的功能由各个厂商完成,以MySQL的实现为例。
public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }}
当我们执行Class.forName("com.mysql.jdbc.Driver")方法的时候,就会执行com.mysql.jdbc.Drvier这个类的静态块中的代码。静态块中的代码只是调用了一下DriverManager的referisterDriver()方法,然后将Driver对象注册到DriverMananger中。我们继续跟进到DriverManager这个类中,来看相关代码:
public class DriverManager { // List of registered JDBC drivers private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>(); /* Prevent the DriverManager class from being instantiated. */ private DriverManager(){} /** * Load the initial JDBC drivers by checking the System property * jdbc.properties and then use the {@code ServiceLoader} mechanism */ static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { registerDriver(driver, null); } public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); }}
在注册之前,将传过来的Driver对象,封装成了一个DriverInfo对象。接下来继续执行客户端代码的第二部,调用DriverManager的getConnection方法获取链接对象,跟进源码:
@CallerSensitive public static Connection getConnection(String url, java.util.Properties info) throws SQLException { return (getConnection(url, info, Reflection.getCallerClass())); } @CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return (getConnection(url, info, Reflection.getCallerClass())); } @CallerSensitive public static Connection getConnection(String url) throws SQLException { java.util.Properties info = new java.util.Properties(); return (getConnection(url, info, Reflection.getCallerClass())); } // Worker method called by the public getConnection() methods. private static Connection getConnection( String url, java.util.Properties info, Class> caller) throws SQLException { /* * When callerCl is null, we should check the application's * (which is invoking this class indirectly) * classloader, so that the JDBC driver class outside rt.jar * can be loaded from here. */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection("" + url + "")"); // Walk through the loaded registeredDrivers attempting to make a connection. // Remember the first exception that gets raised so we can reraise it. SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } // if we got here nobody could connect. if (reason != null) { println("getConnection failed: " + reason); throw reason; } println("getConnection: no suitable driver found for "+ url); throw new SQLException("No suitable driver found for "+ url, "08001"); }
在getConnection()中又会调用各个厂商实现的Driver的connect()方法获得链接对象。这样的话,就巧妙地避开了使用继承,为不同数据库提供相同接口。JDBC API中DriverManager就是桥,如下图所示:
12.7.桥接模式优缺点
优点
- 你可以创建与平台无关的类和程序。
- 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
- 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
- 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。
缺点
- 对高内聚的类使用该模式可能会让代码更加复杂。
12.8.代理、桥接、装饰器、适配器 4 种设计模式的区别
代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
###
代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
12.9.作业
1、完善第三方登录接口,完成不修改接口也能实现自动适配的功能。
通过在接口ILoginForThird的方法参数添加泛型约束。
/** * 第三方登录的接口,方法限制适配器的Class */ public interface ILoginForThird { ResultMsg loginForThird(String id, Class extends ILoginAdapter> clazz); } /** * 第三方登录适配器实现,通过适配器对象判断是否兼容 */ public class LoginForThirdAdapter implements ILoginForThird { @Override public ResultMsg loginForThird(String id, Class extends ILoginAdapter> clazz) { try { ILoginAdapter adapter = clazz.newInstance(); if (adapter.support(adapter)) { return adapter.login(id, adapter); } } catch (Exception e) { e.printStackTrace(); } return null; } } /** * 登录适配器接口 */ public interface ILoginAdapter { Boolean support(Object object); ResultMsg login(String id, Object adapter); } /** * 微博登录适配器 */ public class LoginForWeiboAdapter extends AbstractAdapter { @Override public Boolean support(Object adapter) { return adapter instanceof LoginForWeiboAdapter; } @Override public ResultMsg login(String id, Object adapter) { if (! support(adapter)) { return null; } return super.loginForRegist(id, null); } } /** * 抽象适配器 */ public abstract class AbstractAdapter extends PassportService implements ILoginAdapter { protected ResultMsg loginForRegist(String username, String password) { if(null == password){ password = "THIRD_EMPTY"; } super.regist(username,password); return super.login(username,password); } } // 原有的注册与登录方法 public class PassportService { // 注册方法 public ResultMsg regist(String username, String password) { return new ResultMsg(200, "注册成功", new Member()); } // 登录的方法 public ResultMsg login(String username, String password) { return new ResultMsg(200, "登录成功", new Member()); } }
测试代码
public class Test { public static void main(String[] args) { // 新增微博登录 ILoginForThird loginForThird = new LoginForThirdAdapter(); ResultMsg resultMsg = loginForThird.loginForThird("微博账号", LoginForWeiboAdapter.class); System.out.println(resultMsg.toString()); } }
运行效果
ResultMsg(code=200, msg=登录成功, data=Member(username=null, password=null, mid=null, info=null))
类图
2、说说你对桥接模式的理解。
桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
在 GoF 的《设计模式》一书中,桥接模式被定义为:“将抽象和实现解耦,让它们可以独立变化。”
定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。
而定义中的“实现”,也并非“接口的实现类”,而是的一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。
简单的理解:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”
遵循“组合优于继承”设计原则,通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
参考资料
- Alexander Shvets《Dive into Design Patterns》
- 极客时间《设计模式之美》
- 咕泡学院《适配器模式与桥接模式详解》