八、適配器模式與橋接模式詳解

11.適配器模式

11.1.定義

適配器模式是一種結構型設計模式, 它能使接口不兼容的對象能夠相互合作。

適配器模式的英文翻譯是 Adapter Design Pattern。顧名思義,這個模式就是用來做適配的,它將不兼容的接口轉換為可兼容的接口,讓原本由於接口不兼容而不能一起工作的類可以一起工作。

示意圖

八、適配器模式與橋接模式詳解

生活場景:電源插轉換頭、手機充電轉換頭、顯示器轉接頭。

八、適配器模式與橋接模式詳解

你可以創建一個適配器。 這是一個特殊的對象, 能夠轉換對象接口, 使其能與其他對象進行交互

適配器模式通過封裝對象將複雜的轉換過程藏於幕後。 被封裝的對象甚至察覺不到適配器的存在。

適配器不僅可以轉換不同格式的數據, 其還有助於採用不同接口的對象之間的合作。 它的運作方式如下:

  1. 適配器實現與其中一個現有對象兼容的接口。
  2. 現有對象可以使用該接口安全地調用適配器方法。
  3. 適配器方法被調用後將以另一個對象兼容的格式和順序將請求傳遞給該對象。

有時你甚至可以創建一個雙向適配器來實現雙向轉換調用。

11.2.應用場景

  1. 封裝有缺陷的接口設計
  2. 統一多個類的接口設計
  3. 替換依賴的外部系統
  4. 兼容老版本接口
  5. 適配不同格式的數據

11.3.適配器角色

Adaptee 是一組不兼容 ITarget 接口定義的接口。

ITarget 表示要轉化成的接口定義。

Adapter/Adaptor 將 Adaptee 轉化成一組符合 ITarget 接口定義的接口。

適配器有3中形式:類適配器、對象適配器、接口適配器

11.4.類適配器

類適配器: 基於繼承。

做法:讓Adaptor實現ITarget接口,並且繼承Adaptee,這樣Adaptor就具備ITarget和Adaptee的特性,就可以將兩者進行轉化。

UML類圖:

八、適配器模式與橋接模式詳解

<code> // 類適配器: 基於繼承
 ​
 // 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,這是跟對象適配器最大的不同點
 }/<code>

11.5.對象適配器

對象適配器:基於組合。

做法:讓Adaptor 實現ITarget 接口,然後內部持有Adaptee實例,然後在ITarget接口規定的方法內轉換Adaptee。

URL類圖:

八、適配器模式與橋接模式詳解

<code> // 對象適配器:基於組合
 ​
 // 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();
  }
 }/<code>

11.5.接口適配器

使用接口適配器讓我們只實現我們所需要的接口方法

<code> 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...
  }
 }/<code>

11.6.實戰

重構第三方登錄自由適配場景

首先創建統一登陸結果ResultMsg類:

<code> @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;
    }
 }/<code>

假設老系統的的登錄邏輯PasswordService:

<code> 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;
    }
 }/<code>

遵循開閉原則,老代碼我們不修改。開啟代碼重構之路,創建Member類:

<code> @Data
 public class Member {
 ​
     private String username;
     private String password;
     private String mid;
     private String info;
 }/<code>

運行穩定的代碼不改動。創建ITarget角色IPassportForThird接口。

<code> public interface IPassportForThird {
 ​
     ResultMsg loginForQQ(String openId);
 ​
     ResultMsg loginForWechat(String openId);
 ​
     ResultMsg loginForToken(String token);
 ​
     ResultMsg loginForTelphone(String phone, String code);
 }/<code>

創建適配器兼容Adaptor角色PassportForThirdAdapter類

<code> 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;
    }
 }/<code>

根據不同登錄方式,創建不同登錄Adaptor。首先,創建LoginAdapter接口:

<code>public interface ILoginAdapter {
boolean support(Object object);
ResultMsg login(String id,Object adapter);
}/<code>

創建一個抽象類AbstraceAdapter繼承PassportService原有功能,同時實現ILoginAdapter接口,然後分別實現不同的登錄適配。

QQ登錄

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

手機登錄

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

Token登錄

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

創建適配器PassportForThirdAdapter類,實現目標接口IPassportForThird兼容。

<code>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;
}
}/<code>

客戶端測試代碼

<code>public class Test {
public static void main(String[] args) {
IPassportForThird adapter = new PassportForThirdAdapter();
adapter.loginForQQ("sdfasdfasfasfas");
}
}/<code>

類圖

八、適配器模式與橋接模式詳解

我們為每一個適配器加上了support()方法,用來判斷是否兼容,參數是Object,來源於接口。ILoginAdapter接口是為了代碼規範。上面的代碼綜合了策略模式、簡單工廠和適配器模式。

11.7.源碼

Spring AOP中的AdvisorAdapter。有三個實現類MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter。

頂層接口AdvisorAdapter類

<code>public interface AdvisorAdapter {
boolean supportsAdvice(Advice var1);

MethodInterceptor getInterceptor(Advisor var1);
}/<code>

MethodBeforeAdviceAdapter實現。其餘兩個不貼了。

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

Spring 根據不同的AOP配置確定使用相應的Advice。

下面看SpringMVC的HandlerAdapter類,它也有多個子類。

類圖:

八、適配器模式與橋接模式詳解


他的適配關鍵代碼在DispatcherServlet的doDispatch()方法中,我們看源碼:

<code>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);
}

}
}/<code>

doDispatch方法調用了getHandlerAdaper方法

<code>@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;
}/<code>

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.通用寫法

創建抽象角色

<code>// 抽象
public abstract class Abstraction {

protected IImplementor mImplementor;

public Abstraction(IImplementor implementor) {
this.mImplementor = implementor;
}

public void operation() {
this.mImplementor.operationImpl();
}
}/<code>

修正抽象角色

<code>// 修正抽象
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(IImplementor implementor) {
super(implementor);
}

@Override
public void operation() {
super.operation();
System.out.println("refined operation");
}
}/<code>

抽象實現角色

<code>// 抽象實現
public interface IImplementor {
void operationImpl();
}/<code>

具體實現角色

<code>// 具體實現
public class ConcreteImplementorA implements IImplementor {

public void operationImpl() {
System.out.println("I'm ConcreteImplementor A");
}
}/<code>

測試代碼

<code>public class Test {
public static void main(String[] args) {
// 來一個實現化角色
IImplementor imp = new ConcreteImplementorA();
// 來一個抽象化角色,聚合實現
Abstraction abs = new RefinedAbstraction(imp);
// 執行操作
abs.operation();
}
}/<code>

運行結果

<code>I'm ConcreteImplementor A
refined operation/<code>

12.4.應用場景

  • 拆分或重組一個具有多重功能的龐雜類 (例如能與多個數據庫服務器進行交互的類),可以使用橋接模式。
  • 如果你希望在幾個獨立維度上擴展一個類,可使用該模式。
  • 如果你需要在運行時切換不同實現方法,可使用橋接模式。

12.5.業務場景中的運用

辦公發送郵件、短信消息或者系統消息。緊急程度分為普通消息、緊急消息、特急消息。

八、適配器模式與橋接模式詳解

使用繼承的話,情況複雜,也不利於擴展。通過橋接來解決。

創建一個IMessage接口擔任橋接角色:

<code>public interface IMessage {
//發送消息的內容和接收人
void send(String message,String toUser);
}/<code>

創建郵件消息實現EmailMessage類:

<code>public class EmailMessage implements IMessage {
public void send(String message, String toUser) {
System.out.println("使用郵件消息發送" + message + "給" + toUser);
}
}/<code>

創建手機消息實現SmsMessage類:

<code>public class SmsMessage implements IMessage {
public void send(String message, String toUser) {
System.out.println("使用短信消息發送" + message + "給" + toUser);
}
}/<code>

創建橋接抽象角色AbastractMessage類

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

創建具體實現普通消息類NormalMessage:

<code>public class NomalMessage extends AbastractMessage {
public NomalMessage(IMessage message) {
super(message);
}
}/<code>

創建短信消息類SmsMessage:

<code>public class SmsMessage implements IMessage {
public void send(String message, String toUser) {
System.out.println("使用短信消息發送" + message + "給" + toUser);
}
}/<code>

創建緊急消息UrgencyMessage類:

<code>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;
}
}/<code>

代碼測試

<code>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("加班申請","王總");
}
}/<code>

運行效果:

<code>使用短信消息發送加班申請給王總
使用郵件消息發送【加急】加班申請給王總/<code>

12.6.源碼

JDBC API,其中Driver類就是橋接對象。使用Class.forName方法動態加載各個數據庫廠商的Driver類。

以MySQL的實現為例:

<code>//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語句,並返回結果/<code>

我們看一下Driver接口定義:

<code>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;
}/<code>

Driver在JDBC中沒有做任何實現,具體的功能由各個廠商完成,以MySQL的實現為例。

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

當我們執行Class.forName("com.mysql.jdbc.Driver")方法的時候,就會執行com.mysql.jdbc.Drvier這個類的靜態塊中的代碼。靜態塊中的代碼只是調用了一下DriverManager的referisterDriver()方法,然後將Driver對象註冊到DriverMananger中。我們繼續跟進到DriverManager這個類中,來看相關代碼:

<code>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);

}
}/<driverinfo>/<code>

在註冊之前,將傳過來的Driver對象,封裝成了一個DriverInfo對象。接下來繼續執行客戶端代碼的第二部,調用DriverManager的getConnection方法獲取鏈接對象,跟進源碼:

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

在getConnection()中又會調用各個廠商實現的Driver的connect()方法獲得鏈接對象。這樣的話,就巧妙地避開了使用繼承,為不同數據庫提供相同接口。JDBC API中DriverManager就是橋,如下圖所示:

八、適配器模式與橋接模式詳解

12.7.橋接模式優缺點

優點

  • 你可以創建與平臺無關的類和程序。
  • 客戶端代碼僅與高層抽象部分進行互動, 不會接觸到平臺的詳細信息。
  • 開閉原則。 你可以新增抽象部分和實現部分, 且它們之間不會相互影響。
  • 單一職責原則。 抽象部分專注於處理高層邏輯, 實現部分處理平臺細節。

缺點

  • 對高內聚的類使用該模式可能會讓代碼更加複雜。

12.8.代理、橋接、裝飾器、適配器 4 種設計模式的區別

代理、橋接、裝飾器、適配器,這 4 種模式是比較常用的結構型設計模式。它們的代碼結構非常相似。籠統來說,它們都可以稱為 Wrapper 模式,也就是通過 Wrapper 類二次封裝原始類。

###

代理模式:代理模式在不改變原始類接口的條件下,為原始類定義一個代理類,主要目的是控制訪問,而非加強功能,這是它跟裝飾器模式最大的不同。

橋接模式:橋接模式的目的是將接口部分和實現部分分離,從而讓它們可以較為容易、也相對獨立地加以改變。

裝飾器模式:裝飾者模式在不改變原始類接口的情況下,對原始類功能進行增強,並且支持多個裝飾器的嵌套使用。

適配器模式:適配器模式是一種事後的補救策略。適配器提供跟原始類不同的接口,而代理模式、裝飾器模式提供的都是跟原始類相同的接口。

12.9.作業

1、完善第三方登錄接口,完成不修改接口也能實現自動適配的功能。

通過在接口ILoginForThird的方法參數添加泛型約束。

<code> /**
  * 第三方登錄的接口,方法限制適配器的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());
    }
 }/<code>

測試代碼

<code> public class Test {
     public static void main(String[] args) {
         // 新增微博登錄
         ILoginForThird loginForThird = new LoginForThirdAdapter();
         ResultMsg resultMsg = loginForThird.loginForThird("微博賬號", LoginForWeiboAdapter.class);
         System.out.println(resultMsg.toString());
    }
 }/<code>

運行效果

<code> ResultMsg(code=200, msg=登錄成功, data=Member(username=null, password=null, mid=null, info=null))/<code>

類圖

八、適配器模式與橋接模式詳解

2、說說你對橋接模式的理解。

橋接模式是一種結構型設計模式, 可將一個大類或一系列緊密相關的類拆分為抽象和實現兩個獨立的層次結構, 從而能在開發時分別使用。

橋接模式:

橋接模式的目的是將接口部分和實現部分分離,從而讓它們可以較為容易、也相對獨立地加以改變。

在 GoF 的《設計模式》一書中,橋接模式被定義為:“將抽象和實現解耦,讓它們可以獨立變化。”

定義中的“抽象”,指的並非“抽象類”或“接口”,而是被抽象出來的一套“類庫”,它只包含骨架代碼,真正的業務邏輯需要委派給定義中的“實現”來完成。

而定義中的“實現”,也並非“接口的實現類”,而是的一套獨立的“類庫”。“抽象”和“實現”獨立開發,通過對象之間的組合關係,組裝在一起。

簡單的理解:“一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴展。”

遵循“組合優於繼承”設計原則,通過組合關係來替代繼承關係,避免繼承層次的指數級爆炸。



  1. Alexan­der Shvets《Dive into Design Patterns》
  2. 極客時間《設計模式之美》
  3. 咕泡學院《適配器模式與橋接模式詳解》



分享到:


相關文章: