Java設(shè)計(jì)模式之橋接模式
本文通過老王和小王買車,引出設(shè)計(jì)模式中的結(jié)構(gòu)型設(shè)計(jì)之橋接模式,接著說明設(shè)計(jì)型模式的概念和代碼實(shí)現(xiàn),為了加深理解,會(huì)說明適配器設(shè)計(jì)模式在JDBC中的應(yīng)用,最后談?wù)剺蚪幽J胶瓦m配器模式的總結(jié)。
讀者可以拉取完整代碼到本地進(jìn)行學(xué)習(xí),實(shí)現(xiàn)代碼均測(cè)試通過后上傳到碼云,本地源碼下載。
一、引出問題
老王和小王去奔馳4S店買車,奔馳4S店的各種品牌型號(hào)琳瑯滿目,老王想試駕奔馳E、小王想試駕奔馳G,并且提出兩種奔馳型號(hào)的各種顏色都想體驗(yàn)一把,這讓店小二犯了難,兩兩組合就是很多種,4S店壓根放不下。
無奈店小二求救經(jīng)理,經(jīng)理出了一個(gè)注意:將奔馳E和G開的品牌抽象出來,將顏色也抽象出來,通過品牌和顏色的組合代替繼承關(guān)系,減少了顏色和品牌的耦合,且減少了車的個(gè)數(shù),只需要兩臺(tái)就夠了。
果然經(jīng)理不愧是經(jīng)理。
經(jīng)理所說的其實(shí)就是橋接模式。這種模式涉及到一個(gè)作為橋接的接口,使得實(shí)體類的功能獨(dú)立于接口實(shí)現(xiàn)類。這兩種類型的類可被結(jié)構(gòu)化改變而互不影響。
二、概念與使用
我們看一些概念:橋接(Bridge)是用于把抽象化與實(shí)現(xiàn)化解耦,使得二者可以獨(dú)立變化。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它通過提供抽象化和實(shí)現(xiàn)化之間的橋接結(jié)構(gòu),來實(shí)現(xiàn)二者的解耦。
在該模式中應(yīng)該涉及到四個(gè)角色:
①實(shí)現(xiàn)類接口(Implementor):定義實(shí)現(xiàn)角色的接口,供擴(kuò)展抽象化角色使用,例如抽象出奔馳品牌benz 可以擴(kuò)展出 benzE benzG
②具體實(shí)現(xiàn)角色(ConcreteImplementor):實(shí)現(xiàn)類的具體實(shí)現(xiàn),例如各種奔馳品牌
③抽象化(Abstraction)角色:定義一個(gè)抽象類,其中引用了實(shí)現(xiàn)化角色(想要組合),例如汽車產(chǎn)品
④擴(kuò)展抽象化(RefinedAbstraction)角色:抽象化角色子類,實(shí)現(xiàn)父類方法,且通過組合關(guān)系調(diào)用實(shí)現(xiàn)化角色中的業(yè)務(wù)方法,例如具體奔馳產(chǎn)品,紅色奔馳、白色奔馳
根據(jù)該模式的定義,我們將奔馳品牌抽象出來,然后各品牌有各自的實(shí)現(xiàn),每個(gè)顏色的車把車品牌組合進(jìn)來,在客戶端中每個(gè)相機(jī)類型和相機(jī)品牌都能兩兩組合。
我們看具體的代碼實(shí)現(xiàn):
實(shí)現(xiàn)類接口:
/**
* 奔馳品牌類
* @author tcy
* @Date 05-08-2022
*/
public interface BenzBrand {
void showInfo();
}具體實(shí)現(xiàn)角色1:
/**
* @author tcy
* @Date 05-08-2022
*/
public class BenzE implements BenzBrand{
@Override
public void showInfo() {
System.out.print("【奔馳E】顏色是:");
}
}具體實(shí)現(xiàn)角色2:
/**
* @author tcy
* @Date 05-08-2022
*/
public class BenzG implements BenzBrand{
@Override
public void showInfo() {
System.out.print("【奔馳G】顏色是:");
}
}抽象化角色:
/**
* 抽象奔馳類
* @author tcy
* @Date 05-08-2022
*/
public abstract class Benz {
// 將品牌組合進(jìn)來
protected BenzBrand benzBrand;
public Benz(BenzBrand benzBrand) {
this.benzBrand = benzBrand;
}
public void showInfo(){
benzBrand.showInfo();
}
}擴(kuò)展抽象化1:
/**
* @author tcy
* @Date 05-08-2022
*/
public class BlackBenz extends Benz {
public BlackBenz(BenzBrand benzBrand) {
super(benzBrand);
}
@Override
public void showInfo() {
super.showInfo();
System.out.println("黑色...");
}
}擴(kuò)展抽象化2:
/**
* @author tcy
* @Date 05-08-2022
*/
public class RedBenz extends Benz {
public RedBenz(BenzBrand benzBrand) {
super(benzBrand);
}
@Override
public void showInfo() {
super.showInfo();
System.out.println("紅色...");
}
}客戶端調(diào)用:
/**
* @author tcy
* @Date 05-08-2022
*/
public class Client {
public static void main(String[] args) {
// 黑色奔馳E
Benz benz1 = new BlackBenz(new BenzE());
benz1.showInfo();
// 黑色奔馳G
Benz benz2 = new BlackBenz(new BenzG());
benz2.showInfo();
// 紅色奔馳E
Benz benz3 = new RedBenz(new BenzE());
benz3.showInfo();
// 紅色奔馳G
Benz benz4 = new RedBenz(new BenzG());
benz4.showInfo();
}
}【奔馳E】顏色是:黑色...
【奔馳G】顏色是:黑色...
【奔馳E】顏色是:紅色...
【奔馳G】顏色是:紅色...
這樣即使老王提出來新的顏色、新的車型,只需要增加相應(yīng)的具體實(shí)現(xiàn)角色或者擴(kuò)展抽象化角色即可。
顧名思義,橋接模式就像是一個(gè)橋,可以用來連接兩個(gè)不同地方,這兩個(gè)地方自由發(fā)展,中間的貿(mào)易是通過一座橋來連接。
這種方法的缺點(diǎn)也很顯著,汽車能很快的確立型號(hào)和顏色兩個(gè)維度,在實(shí)際業(yè)務(wù)開發(fā)中,識(shí)別出系統(tǒng)兩個(gè)獨(dú)立變化的維度就不簡(jiǎn)單了。
不難看出,列舉的例子有些過于強(qiáng)求,在現(xiàn)實(shí)世界中是永遠(yuǎn)不可能發(fā)生的,為了加深理解我找了大量在JDK亦或是Spirng等各種框架對(duì)橋接模式的應(yīng)用,只找到了橋接模式在Jdbc中的應(yīng)用。
三、應(yīng)用
我們都知道通過JDBC可以完成Java對(duì)關(guān)系型數(shù)據(jù)庫的SQL操作,我們?cè)谶B接數(shù)據(jù)數(shù)據(jù)庫時(shí),想必都接觸過Driver,在連接MySQL和Oracle的Driver都是不同的,這些都是實(shí)現(xiàn)接口類。
我們看一下MySQL中實(shí)現(xiàn)的Driver類。
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!");
}
}
}在該類中實(shí)際上有兩個(gè)作用,一是調(diào)用了DriverManager中的registerDriver方法來注冊(cè)驅(qū)動(dòng),二是當(dāng)驅(qū)動(dòng)注冊(cè)完成后,我們就會(huì)開始調(diào)用DriverManager中的getConnection方法了。
我們看DriverManager的完整代碼:
public class DriverManager {
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()));
}
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");
}
}
}在Java中通過Connection提供給各個(gè)數(shù)據(jù)庫一樣的操作接口,這里的Connection可以看作抽象類。
可以說我們用來操作不同數(shù)據(jù)庫的方法都是相同的,不過MySQL有自己的ConnectionImpl類,同樣Oracle也有對(duì)應(yīng)的實(shí)現(xiàn)類。
這里Driver和Connection之間是通過DriverManager類進(jìn)行橋接的,這種橋接模式和我們上面可以清晰的看出來各個(gè)角色是不同的。
四、總結(jié)
橋接模式是很好理解的,相信認(rèn)真看了實(shí)例的同學(xué)應(yīng)該都可以看懂,但那并不代表你已經(jīng)掌握了該設(shè)計(jì)模式。在我們使用JDBC的時(shí)候,想必有很多同學(xué)并不能看出來這是橋接模式。
紙上得來終覺淺,有一部分例子是為了說明橋接模式而“構(gòu)想”出來的,各個(gè)角色都是清晰直觀??戳诉@樣的代碼,可以學(xué)會(huì)橋接模式,但是到了實(shí)際中很可能還是不會(huì)用。
最好的方法就是給出真實(shí)項(xiàng)目里的例子。但是這個(gè)難度確實(shí)很大,一到了真實(shí)項(xiàng)目里,就會(huì)遇到很多細(xì)節(jié)問題,從而影響對(duì)模式的理解,而且真實(shí)項(xiàng)目都帶有一定的業(yè)務(wù)環(huán)境。
看懂并且學(xué)會(huì)了設(shè)計(jì)模式是一回事,在實(shí)際開發(fā)中擇優(yōu)選擇設(shè)計(jì)模式那是另外一回事,這不僅需要對(duì)各個(gè)設(shè)計(jì)模式理解到位,更多的是對(duì)業(yè)務(wù)的理解和代碼理念的把控。
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
Spring Boot 中的Servlet簡(jiǎn)單使用
在spring boot中添加自己的Servlet有兩種方法,代碼注冊(cè)Servlet和注解自動(dòng)注冊(cè)(Filter和Listener也是如此)。這篇文章主要介紹了Spring Boot 中的Servlet簡(jiǎn)單使用,需要的朋友可以參考下2017-04-04
Java調(diào)用參數(shù)類型是application/x-www-form-urlencoded的API問題
在使用Postman進(jìn)行接口測(cè)試時(shí),對(duì)于POST請(qǐng)求,需將請(qǐng)求頭設(shè)置為application/x-www-form-urlencoded,并將參數(shù)轉(zhuǎn)為String類型,通常在GET請(qǐng)求中,參數(shù)直接拼接在URL后,本文通過具體實(shí)例,詳細(xì)講解了參數(shù)處理的方法,適合API開發(fā)者參考2024-09-09
Java實(shí)現(xiàn)簡(jiǎn)易版猜燈謎游戲的示例代碼
燈謎是中秋節(jié)傳統(tǒng)的活動(dòng)之一,而現(xiàn)代化的方式則是將其制作成一個(gè)小游戲,讓用戶在游戲的過程中猜燈謎,互動(dòng)體驗(yàn)更佳,所以本文小編就用Java制作一款猜燈謎小游戲吧2023-09-09
淺談springboot內(nèi)置tomcat和外部獨(dú)立部署tomcat的區(qū)別
這篇文章主要介紹了淺談springboot內(nèi)置tomcat和外部獨(dú)立部署tomcat的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
SpringBoot報(bào)錯(cuò)Invalid?bound?statement?(not?found)問題排查和解決方案
這篇文章主要介紹了SpringBoot報(bào)錯(cuò)Invalid?bound?statement?(not?found)問題排查和解決方案,文中通過圖文結(jié)合的方式講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-03-03
淺談選擇結(jié)構(gòu)if語句和switch語句的區(qū)別
下面小編就為大家?guī)硪黄獪\談選擇結(jié)構(gòu)if語句和switch語句的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07
Springboot實(shí)現(xiàn)根據(jù)條件切換注入不同實(shí)現(xiàn)類的示例代碼
這篇文章主要介紹了Springboot實(shí)現(xiàn)根據(jù)條件切換注入不同實(shí)現(xiàn)類的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
java實(shí)現(xiàn)乘地鐵方案的最優(yōu)選擇(票價(jià),距離)
這篇文章主要介紹了java實(shí)現(xiàn)乘地鐵方案的最優(yōu)選擇(票價(jià),距離),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
Sa-Token中的SaSession對(duì)象使用學(xué)習(xí)示例詳解
這篇文章主要為大家介紹了Sa-Token中的SaSession對(duì)象使用學(xué)習(xí)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
java線程并發(fā)控制同步工具CountDownLatch
這篇文章主要為大家介紹了java線程并發(fā)控制同步工具CountDownLatch使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

