设计模式之适配器模式与桥接模式详解和应用

news2024/9/24 3:24:00

目录

  • 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 桥接模式优缺点
    • 2.8 代理、桥接、装饰器、适配器 4 种设计模式的区别


1 适配器模式

1.1 定义

适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。

适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

示意图

img

**生活场景:**电源插转换头、手机充电转换头、显示器转接头。

img

你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互

适配器模式通过封装对象将复杂的转换过程藏于幕后。 被封装的对象甚至察觉不到适配器的存在。

适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:

  1. 适配器实现与其中一个现有对象兼容的接口。
  2. 现有对象可以使用该接口安全地调用适配器方法。
  3. 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。

有时你甚至可以创建一个双向适配器来实现双向转换调用。

1.2 应用场景

  1. 封装有缺陷的接口设计
  2. 统一多个类的接口设计
  3. 替换依赖的外部系统
  4. 兼容老版本接口
  5. 适配不同格式的数据

1.3 适配器角色

Adaptee 是一组不兼容 ITarget 接口定义的接口。

ITarget 表示要转化成的接口定义。

Adapter/Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口。

适配器有3中形式:类适配器、对象适配器、接口适配器

1.4 类适配器

类适配器: 基于继承。

做法:让Adaptor实现ITarget接口,并且继承Adaptee,这样Adaptor就具备ITarget和Adaptee的特性,就可以将两者进行转化。

UML类图:

img

// 类适配器: 基于继承

// 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类图:

img

// 对象适配器:基于组合

// 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
        //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");
    }
}

类图

img

我们为每一个适配器加上了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类,它也有多个子类。

类图:

img

他的适配关键代码在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 种设计模式中最难理解的模式之一了。我查阅了比较多的书籍和资料之后发现,对于这个模式有两种不同的理解方式。

示意图

img

当然,这其中“最纯正”的理解方式,当属 GoF 的《设计模式》一书中对桥接模式的定义。毕竟,这 23 种经典的设计模式,最初就是由这本书总结出来的。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”

关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则,所以,这里我就不多解释了。我们重点看下 GoF 的理解方式。

桥接模式通过组合关系来替代继承关系,避免继承层次的指数级爆炸。

2.2 角色

类图:

img

桥接模式有四个角色:

抽象(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);
    }

    @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 A
refined operation

复制

2.4 应用场景

  • 拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类),可以使用桥接模式。
  • 如果你希望在几个独立维度上扩展一个类,可使用该模式。
  • 如果你需要在运行时切换不同实现方法,可使用桥接模式。

2.5 业务场景中的运用

办公发送邮件、短信消息或者系统消息。紧急程度分为普通消息、紧急消息、特急消息。

img

使用继承的话,情况复杂,也不利于扩展。通过桥接来解决。

创建一个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 drivers
    private 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 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就是桥,如下图所示:

img

2.7 桥接模式优缺点

优点

  • 你可以创建与平台无关的类和程序。
  • 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
  • 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
  • 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。

缺点

  • 对高内聚的类使用该模式可能会让代码更加复杂。

2.8 代理、桥接、装饰器、适配器 4 种设计模式的区别

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

**代理模式:**代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

**桥接模式:**桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

**装饰器模式:**装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

**适配器模式:**适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/355295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

指针笔记(指针数组和指向数组的指针,数组中a和a的区别等)

指针数组和指向数组的指针 int *p[4]和int (*p)[4]有何区别&#xff1f; 前者是一个指针数组&#xff0c;数组大小为4&#xff0c;每一个元素都是一个指向int的指针 后者是指向int[4]类型数组的指针 以上代码若运行会报如下错误 main函数中定义的a数组本质是一个指向int[2]的…

内网渗透(三十八)之横向移动篇-pass the key 密钥传递攻击(PTK)横向攻击

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

从0到1一步一步玩转openEuler--18 openEuler 管理服务-简介

文章目录18 管理服务简介18.1 概念介绍18 管理服务简介 systemd是在Linux下&#xff0c;与SysV和LSB初始化脚本兼容的系统和服务管理器。systemd使用socket和D-Bus来开启服务&#xff0c;提供基于守护进程的按需启动策略&#xff0c;支持快照和系统状态恢复&#xff0c;维护挂…

java基础学习 day41(继承中成员变量和成员方法的访问特点,方法的重写)

继承中&#xff0c;成员变量的访问特点 a. name前什么都不加&#xff0c;name变量的访问采用就近原则&#xff0c;先在局部变量中查找&#xff0c;若没找到&#xff0c;继续在本类的成员变量中查找&#xff0c;若没找到&#xff0c;继续在直接父类的成员变量中查找&#xff0c…

Mel Frequency Cepstral Coefficients (MFCCs)

wiki里说 在声音处理中&#xff0c;梅尔频率倒谱( MFC ) 是声音的短期功率谱的表示&#xff0c;基于非线性梅尔频率标度上的对数功率谱的线性余弦变换。 倒谱和MFC 之间的区别在于&#xff0c;在 MFC 中&#xff0c;频带在梅尔尺度上等距分布&#xff0c;这比正常频谱中使用的线…

Windows10 安装ElasticStack8.6.1

一、安装ElasticSearch8.6.1 1.官网下载ElasticSearch8.6.1压缩包后解压 2.安装为服务 elasticsearch-service.bat install 3.运行 elasticsearch-service.bat start 4.通过浏览器访问 http://localhost:9200/ 提示需要登录&#xff0c;但不知密码是啥。 5.重置密码 ela…

操作系统(day12)-- 基本分段存储,段页式存储

基本分段存储管理方式 不会产生内部碎片&#xff0c;会产生外部碎片 分段 按照程序自身的逻辑关系划分为 若干个段&#xff0c;每个段都有一个段名&#xff0c;每段从0开始编址 分段存储管理方式中一个段表项由段号&#xff08;隐含&#xff09;、段长、基地址 分段的段表项固…

Python基础2

1. python函数定义 函数定义语法&#xff1a; def 函数名&#xff08;传入参数&#xff09;&#xff1a; 函数体 return 返回值 —————————————— 参数如果不需要&#xff0c;可以省略返回值如果不需要&#xff0c;可以省略函数必须先定义在使用 注意&#xff…

UIE微调:autoML平台实践(一)

参考&#xff1a;uie模型微调个人总结 github&#xff1a;https://github.com/PaddlePaddle/PaddleNLP/tree/develop/model_zoo/uie 1、显存问题 之前是在实验室的服务器上跑&#xff08;2080ti&#xff0c;12G显存&#xff09;&#xff0c;频频出现以下报错&#xff1a; 原…

python 刷题时常见的函数

collections.OrderedDict 1. move_to_end() move_to_end() 函数可以将指定的键值对移动到最前面或者最后面&#xff0c;即最左边或最右边 。 2. popitem() popitem()可以完成元素的删除操作&#xff0c;有一个可选参数last&#xff08;默认为True&#xff09;&#xff0c;…

微软?还是Linux?

导读本周&#xff0c;微软宣布它成为了 Linux 基金会的白金成员&#xff0c;这距其前 CEO 巴尔默将 Linux 称之为“癌症”才 15 年。虽然此举对微软来说意义重大&#xff0c;但是并不是开源界的每个人都认为这对于 Linux 来说是好的变化&#xff0c;特别是这家位于雷德蒙的软件…

kafka-3-kafka应用的核心要点和内外网访问

kafka实战教程(python操作kafka)&#xff0c;kafka配置文件详解 Kafka内外网访问的设置 1 kafka简介 根据官网的介绍&#xff0c;ApacheKafka是一个分布式流媒体平台&#xff0c;它主要有3种功能&#xff1a; (1)发布和订阅消息流&#xff0c;这个功能类似于消息队列&#x…

宁波大学2023年MBA招生考试初试成绩查询的通知

根据往年的情况&#xff0c;2023宁波大学MBA考试初试成绩可能将于2月21日公布&#xff0c;最早于20号出来&#xff0c;为了广大考生可以及时查询到自己的分数&#xff0c;杭州达立易考教育为大家汇总了信息。 宁波大学2023年全国硕士研究生招生考试初试成绩将于2月21日公…

Java Type类

文章目录Type简介Type分类1. 原始类型(Class)2. 参数化类型(ParameterizedType)3. 类型变量(TypeVariable)4. 通配符类型(WildcardType)5. 泛型数组类型(GenericArrayType)Type简介 Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型…

Java数据结构-栈、队列常用类(Stack、ArrayDeque、LinkedLList)

数据结构的三要素包括&#xff1a;逻辑结构、存储结构、数据的运算。逻辑结构描述的是数据之间的逻辑关系&#xff0c;分为线性结构&#xff08;线性表&#xff08;数组、链表&#xff09;、栈、队列&#xff09;和非线性结构&#xff08;图、树、集合&#xff09;。物理结构也…

服务器是干什么用的?

首先&#xff0c;什么是服务器&#xff1f;服务器是提供计算服务器和网络服务的设备。服务器和计算机由CPU、硬盘、内存、系统总线等组成。比如我们访问一个网站&#xff0c;点击这个网站会发出访问请求&#xff0c;服务器会响应服务请求&#xff0c;进行相应的处理&#xff0c…

在windows中使用tomcat搭建Jenkins

1、 准备环境&#xff1a;JDK JDK官网下载&#xff1a;https://download.oracle.com/java/19/latest/jdk-19_windows-x64_bin.msi 2、 tomcat包 tocat官网下载&#xff1a;https://tomcat.apache.org/download-90.cgi 3、 Jenkins.war包 Jenkins官网下载&#xff1a;https://mi…

GEE学习笔记 七十六:【GEE之Python版教程十】字典

python中万物皆对象&#xff0c;字典在其他的语言中也有称之为对象&#xff0c;无论怎么称呼它其实就是 {key:value} 这种格式。 依然是运行代码前先注册GEE import ee ee.Initialize() 1、字典的API 首先看一下GEE的python版API&#xff08;Welcome to GEE-Python-API’s d…

曾经的月薪3K,现在的月薪25K,我的测试经验值得你的借鉴

成功不能复制&#xff0c;但经验却可以借鉴&#xff01;从曾经的月薪3K&#xff0c;到现在的月薪25K&#xff0c;我觉得我的涨薪之路的的确确可以给很多人一些参考。授人以鱼不如授人以渔&#xff0c;所以&#xff0c;今天我也想把我的经验分享给大家&#xff0c;希望可以帮助一…

Unity New Input System

安装 Input System是新的输入系统&#xff0c;用来取代旧的Input Manager&#xff0c;方便接收不同的输入设备 在Package Manager里安装 安装后可以选择只使用新的Input System&#xff0c;或选Both两者都使用&#xff0c;考虑很多插件还在使用旧的Input Manager&#xff0c…