设计模式之适配器模式与桥接模式详解和应用
目录
1 适配器模式
1.1 定义
适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
示意图
**生活场景:**电源插转换头、手机充电转换头、显示器转接头。
你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程藏于幕后。 被封装的对象甚至察觉不到适配器的存在。
适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时你甚至可以创建一个双向适配器来实现双向转换调用。
1.2 应用场景
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
- 适配不同格式的数据
1.3 适配器角色
Adaptee 是一组不兼容 ITarget 接口定义的接口。
ITarget 表示要转化成的接口定义。
Adapter/Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口。
适配器有3中形式:类适配器、对象适配器、接口适配器
1.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,这是跟对象适配器最大的不同点
}
复制
1.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();}
}
复制
1.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...}
}
1.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//timereturn 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接口是为了代码规范。上面的代码综合了策略模式、简单工厂和适配器模式。
1.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方法
@Nullable
protected 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是在初始化时早就赋了值的。只有源码专题继续讲解。
1.8 适配器与装饰器的对比
两者都是包装模式
适配器 | 装饰器 | |
---|---|---|
形式 | 适配器没有层级关系,装饰器有层级关系。 | 特殊的适配器 |
定义 | 适配器与被适配者没有必然联系,通常通过继承或代理进行包装 | 装饰器与被装饰者实现同一个接口,目的是为了扩展后保留OOP关系 |
关系 | has-a的关系 | is-a的关系 |
功能 | 注重兼容、转换 | 注重覆盖、扩展 |
设计 | 后置考虑 | 前置考虑 |
1.9 适配器模式的优缺点
优点:
- 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离。
- 开闭原则。只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
缺点:
- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
1.10 总结
一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”,如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。那在实际的开发中,什么情况下才会出现接口不兼容呢?我总结下了下面这样 5 种场景:
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
- 适配不同格式的数据
2 桥接模式
先复习代理模式。它在不改变原始类(或者叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。代理模式在平时的开发经常被用到,常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。
今天学习桥接模式。桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,所以,相当于代理模式来说,桥接模式在实际的项目中并没有那么常用,你只需要简单了解,见到能认识就可以,并不是我们学习的重点。
2.1 原理解析
桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。这个模式可以说是 23 种设计模式中最难理解的模式之一了。我查阅了比较多的书籍和资料之后发现,对于这个模式有两种不同的理解方式。
示意图
当然,这其中“最纯正”的理解方式,当属 GoF 的《设计模式》一书中对桥接模式的定义。毕竟,这 23 种经典的设计模式,最初就是由这本书总结出来的。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”
关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则,所以,这里我就不多解释了。我们重点看下 GoF 的理解方式。
桥接模式通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
2.2 角色
类图:
桥接模式有四个角色:
抽象(Abstraction):该类持有一个对实现角色的引用,抽象角色中的方法需要实现角色。该类一般为抽象类。
修正抽象角色(RefinedAbstraction):Abstraction的具体实现,对Abstraction方法进行完善与扩展。
抽象实现角色(Implementor):确定实现维度的基本操作,提供给Abstraction使用。该类一般为接口或抽象类。
具体实现角色(ConcreteImplementor):Implementor的具体实现。
2.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);}@Overridepublic 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 A
refined operation
复制
2.4 应用场景
- 拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类),可以使用桥接模式。
- 如果你希望在几个独立维度上扩展一个类,可使用该模式。
- 如果你需要在运行时切换不同实现方法,可使用桥接模式。
2.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("加班申请","王总");}
}
运行效果:
使用短信消息发送加班申请给王总
使用邮件消息发送【加急】加班申请给王总
2.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语句的对象Statement
Statement 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 driversprivate final static CopyOnWriteArrayList<DriverInfo> 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 DriverManagerthrow new NullPointerException();}println("registerDriver: " + driver);}
}
在注册之前,将传过来的Driver对象,封装成了一个DriverInfo对象。接下来继续执行客户端代码的第二部,调用DriverManager的getConnection方法获取链接对象,跟进源码:
@CallerSensitivepublic static Connection getConnection(String url,java.util.Properties info) throws SQLException {return (getConnection(url, info, Reflection.getCallerClass()));}@CallerSensitivepublic 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()));}@CallerSensitivepublic 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就是桥,如下图所示:
2.7 桥接模式优缺点
优点
- 你可以创建与平台无关的类和程序。
- 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
- 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
- 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。
缺点
- 对高内聚的类使用该模式可能会让代码更加复杂。
2.8 代理、桥接、装饰器、适配器 4 种设计模式的区别
代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
**代理模式:**代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
**桥接模式:**桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
**装饰器模式:**装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
**适配器模式:**适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
相关文章:
设计模式之适配器模式与桥接模式详解和应用
目录1 适配器模式1.1 定义1.2 应用场景1.3 适配器角色1.4 类适配器1.5 对象适配器1.5 接口适配器1.6 实战1.7 源码1.8 适配器与装饰器的对比1.9 适配器模式的优缺点1.10 总结2 桥接模式2.1 原理解析2.2 角色2.3 通用写法2.4 应用场景2.5 业务场景中的运用2.6 源码2.7 桥接模式优…...
Winform控件开发(14)——NotifyIcon(史上最全)
前言: 先看个气泡提示框的效果: 代码如下: 在一个button中注册click事件,当我们点击button1时,就能显示气泡 private void button1_Click(object sender, EventArgs e){notifyIcon1.Visible = true;notifyIcon1...
Verilog 学习第四节(从计数器到可控制线性序列机——LED实验进化六部曲)
从计数器到可控制线性序列机——LED实验进化六部曲一:让LED灯按照亮0.25s,灭0.75s的状态循环亮灭二:让LED灯按照亮0.25s,灭0.5s,亮0.75s,灭1s的状态循环亮灭三:让LED灯按照指定的亮灭模式亮灭&a…...
操作SSH无密登录配置
例如小编有三台服务器需要相互访问,就需要配置三台,这三台分别是hadoop102,hadoop103 , hadoop1041.打开三个服务器,分别生成hadoop102,hadoop103 , hadoop104的公钥和私钥输入命令,然后一直回车,这时候什么…...
Websocket详细介绍
需求背景 在某个资产平台,在不了解需求的情况下,我突然接到了一个任务,让我做某个页面窗口的即时通讯,想到了用websocket技术,我从来没用过,被迫接受了这个任务,我带着浓烈的兴趣,就…...
大数据书单(100本)
大数据书单(100本) 序号 书名 作者 出版社 1 Hadoop权威指南:大数据的存储与分析(第4版)(修订版)(升级版) Tom White 清华大学出版社 2 Hive编程指南 卡普廖洛 (Edward Capriolo) / 万普勒 (Dean Wampler) / 卢森格林 (Jason Rutherglen) / 曹坤 人民邮…...
python实战应用讲解-【语法基础篇】初识Python(附示例代码)
目录 前言 Python基础 基本概念: 为什么使用Python? Python2.x与3.x版本区别...
【2023保研夏令营】网安、CS(西交、华师、科、南等)
文章目录一、基本情况二、投递和入营情况三、考核情况1. 西交软院(面试)2. 川大网安(笔试面试)3. 华东师范数据学院(机试面试)4. 人大信息学院专硕(机试面试,保密)5. 南大…...
Qt COM组件导出源文件
文章目录摘要dumpcpp.exe注册COM组件COM 组件转CPP参考关键字: Qt、 COM、 组件、 源文件、 dumpcpp摘要 由于厂家提供的库不是纯净C库,是基于COM组件开的库,在和厂家友好交流无果下,只能研究下Qt 如何调用,好在Qt 的…...
各数据库数据类型的介绍和匹配
各数据库数据类型的介绍和匹配1. Oracle的数据类型2. Mysql的数据类型3. Sql server的数据类型4. 类型匹配5. Awakening1. Oracle的数据类型 数据类型介绍 VARCHAR2 :可变长度的字符串 最大长度4000 bytes 可做索引的最大长度749; NCHAR :根据字符集而定的固定长度字…...
Rancher 部署 MySQL
文章目录创建 pvc部署 MySQL前置条件:安装 rancher,可参考文章 docker 部署 rancher 创建 pvc MySQL 数据库是需要存储的,所以必须先准备 pvc 创建 pvc 自定义 pvc 名称选择已经新建好的 storageclass,storageclass 的创建可参考…...
Python语言零基础入门教程(二十五)
Python OS 文件/目录方法 Python语言零基础入门教程(二十四) 39、Python os.openpty() 方法 概述 os.openpty() 方法用于打开一个新的伪终端对。返回 pty 和 tty的文件描述符。 语法 openpty()方法语法格式如下: os.openpty()参数 无 返…...
蓝桥杯算法训练合集十五 1.打翻的闹钟2.智斗锅鸡3.文件列表
目录 1.打翻的闹钟 2.智斗锅鸡 3.文件列表 1.打翻的闹钟 问题描述 冯迭伊曼晚上刷吉米多维奇刷的太勤奋了,几乎天天迟到。崔神为了让VonDieEman改掉迟到的坏毛病,给他买了个闹钟。 一天早上,老冯被闹钟吵醒,他随手将闹钟按掉丢…...
CPU扫盲-CPU与指令集
指令集架构就像是特定的CPU的设计图纸,它规定了这个CPU需要支持那些指令、寄存器有那些状态以及输入输出模型。根据指令集结构的设计,在CPU上通过硬件电路进行实现,就得到了支持该指令集的CPU。指令集就像是我们编程语言中的接口,…...
VINS-Mono/Fusion与OpenCV去畸变对比
VINS中没有直接使用opencv的去畸变函数,而是自己编写了迭代函数完成去畸变操作,主要是为了加快去畸变计算速度 本文对二者的结果精度和耗时进行了对比 VINS-Mono/Fusion与OpenCV去畸变对比1 去畸变原理2 代码实现2.1 OpenCV去畸变2.2 VINS去畸变3 二者对…...
jmx prometheus引起的一次cpu飙高
用户接入了jmx agent进行prometheus监控后,在某个时间点出现cpu飙高 排查思路: 1、top,找到java进程ID 2、top -Hp 进程ID,找到java进程下占用高CPU的线程ID 3、jstack 进程ID,找到那个高CPU的线程ID的堆栈。 4、分析堆…...
Android 虚拟 A/B 详解(六) SnapshotManager 之状态数据
本文为洛奇看世界(guyongqiangx)原创,转载请注明出处。 原文链接:https://blog.csdn.net/guyongqiangx/article/details/129094203 Android 虚拟 A/B 分区《AAndroid 虚拟 A/B 分区》系列,更新中,文章列表: Android 虚拟分区详解(一) 参考资料推荐Android 虚拟分区详解(二…...
Python快速入门系列之一:Python对象
Python对象1. 列表(list)2. 元组(tuple)3. 字典(dict)4. 集合(set)5. 字符串(string)6. BIF (Built-in Function)7. 列表、集合以及字…...
【博客626】不同类型的ARP报文作用以及ARP老化机制
不同类型的ARP报文作用以及ARP老化机制 1、ARP协议及报文 2、不同类型的ARP报文作用 3、ARP工作原理 4、ARP老化机制 5、Linux ARP老化机制 ARP状态机: 在上图中,我们看到只有arp缓存项的reachable状态对于外发包是可用的,对于stale状态的…...
nacos discovery和config
微服务和nacos版本都在2.x及之后。1、discovery用于服务注册,将想要注册的服务注册到nacos中,被naocs发现。pom引入的依赖是:yml配置文件中:2、config用于获取nacos配置管理->配置列表下配置文件中的内容pom引入的依赖是&#…...
【算法数据结构体系篇class06】:堆、大根堆、小根堆、优先队列
一、堆结构1)堆结构就是用数组实现的完全二叉树结构2)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆3)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆4)堆结构的heapInsert与heapify操作5)堆结构的增大ad…...
试题 算法提高 最小字符串
资源限制内存限制:256.0MB C/C时间限制:2.0s Java时间限制:6.0s Python时间限制:10.0s问题描述给定一些字符串(只包含小写字母),要求将他们串起来构成一个字典序最小的字符串。输入格式第一行T,表示有T组数据。接下来T…...
已解决ImportError: cannot import name ‘featureextractor‘ from ‘radiomics‘
已解决from radiomics import featureextractor导包,抛出ImportError: cannot import name ‘featureextractor‘ from ‘radiomics‘异常的正确解决方法,亲测有效!!! 文章目录报错问题报错翻译报错原因解决方法联系博…...
乡村振兴研究:全网最全指标农村经济面板数据(2000-2021年)
数据来源:国家统计局 时间跨度:2000-2021年 区域范围:全国31省 指标说明: 部分样例数据: 行政区划代码地区年份经度纬度乡镇数(个)乡数(个)镇数(个)村民委员会数(个)乡村户数(万户)乡村人口(万人)乡村从业人员(万人…...
C语言中用rand()函数产生一随机数
在C语言中如何产生一个随机数呢?用rand()函数。 rand()函数在头文件:#include <stdio.h>中,函数原型:int rand(void);。rand()会返回一个范围在0到RAND_MAX(32767)之间的随机数(整数&…...
关于系统架构
1.系统架构分类: C/S架构 B/S架构 2.C/S架构 Client / Server(客户端 / 服务器) 特点:需要安装特定的客户端软件。 C/S架构的系统优点和缺点: 优点: 1)速度快(软件中数据大部分都是集成到客户端当中,很少量的数据从服…...
LeetCode 1237. 找出给定方程的正整数解
原题链接 难度:middle\color{orange}{middle}middle 2023/2/18 每日一题 题目描述 给你一个函数 f(x,y)f(x, y)f(x,y) 和一个目标结果 zzz,函数公式未知,请你计算方程 f(x,y)zf(x,y) zf(x,y)z 所有可能的正整数 数对 xxx 和 yyy。满足条件…...
【ArcGIS Pro二次开发】(5):UI管理_自定义控件的位置
新增的自定义控件一般放在默认的【加载项】选项卡下,但是根据需求,我们可能需要将控件放在新的自定义选项卡下,在自定义选项卡添加系统自带的控件,将自定义的按钮等控件放在右键菜单栏里以方便使用,等等。 下面就以一…...
学习OpenGL图形2D/3D编程
环境:WindowsVisual Studio 2019最流行的几个库:GLUT,SDL,SFML和GLFWGLFWGLAD库查看显卡OPENGL支持情况VS2019glfwgladopenGL3.3顶点着色器片段着色器VAO-VBO-(EBO)->渲染VAO-VBO-EBO->texture纹理矩阵matrix对图形transfor…...
2023美赛思路 | A题时间序列预测任务的模型选择总结
2023美赛思路 | A题时间序列预测任务的模型选择总结 目录 2023美赛思路 | A题时间序列预测任务的模型选择总结基本介绍数据描述任务介绍时序模型基本介绍 这道题分析植被就行,主要涉及不同植被间的相互作用,有竞争有相互促进,我查了下“植物科学数据中心”和“中国迁地保护植…...
免费的网站推广 外贸/网络优化的三个方法
php Undefined index和Undefined variable的解决方法$act$_POST[act];用以上代码总是提示Notice: Undefined index: act in F:\windsflybook\post.php on line 18另外,有时还会出现引用内容Notice: Undefined variable: Submit ......等一些这样的提示原因ÿ…...
手机网站app制作/天津百度推广电话号码
修复此问题 禁用内核调试,在 Visual Studio 中调试。- 或 -使用内核调试器而不是 Visual Studio 进行调试。- 或 -在内核调试器中禁用用户模式异常。在当前会话中禁用内核调试 在命令提示处,键入: 复制代码 Kdbgctrl.exe -d对所有会话禁用内核…...
wordpress慢 数据库/seo网站优化助理
本文简介decorator模块是 Michele Simionato 为简化python的decorator的使用难度而开发的,使用它,您可以更加容易的使用decorator机制写出可读性、可维护性更好的代码。本文大部分翻译自下面这篇文档: www.phyast.pitt.edu/~micheles/python/documentati…...
网站导航是什么/百度入口官网
做csdn很久了,听到粉丝问的最多的问题就是:有没有新的完整的项目,因为现在很多流传的项目都太老了,实战意义不是很强。很多程序员每项技术单独拿出来有可能很厉害,例如:Spring Cloud、Spring Boot、Redis、…...
wordpress 封包apk/阿里指数在线查询
C语言中有许多基本的数据类型,如char,int,double等。char是字符类型,可以保存一个字符,int是整数类型,可以保存一个整数,double是双精度实型,可以保存一个实数。例如:cha…...
表白时刻网站/济南百度公司
#css基本语法及页面引用 ##css基本语法 css的定义方法是: 选择器 { 属性:值; 属性:值; 属性:值;} 选择器是将样式和页面元素关联起来的名称,属性是希望设置的样式属性每个属性有一个或多个值。代码示例: div{ width:100px; height:100px; …...