Java 你知道什麼是耦合、如何解(降低)耦合

什麼是耦合性

耦合性(Coupling),也叫耦合度,是對模塊間關聯程度的度量。模塊間的耦合度是指模塊之間的依賴關系,包括控制關系、調用關系、數據傳遞關系。模塊間聯系越多,其耦合性越強,同時表明其獨立性越差( 降低耦合性,可以提高其獨立性)。

什麼是程序間的耦合

假如:當我去new一個對象的時候,而這個對象不存在,這個時候程序會報編譯時異常,也就意味著程序連運行都運行不瞭,我們可以理解為他們的耦合度較高。

如何解耦

我們可以利用Java的反射技術,通過類定名,來進行反射創建對象,這個時候我們可以成功的避免編譯時異常,並且保證瞭項目在這個時候還能正常運行。

工廠模式解耦

在實際開發中我們可以把三層的對象都使用配置文件配置起來,當啟動服務器應用加載的時候,讓一個類中的方法通過讀取配置文件,把這些對象創建出來並存起來。在接下來的使用的時候,直接拿過來用就好瞭。那麼,這個讀取配置文件,創建和獲取三層對象的類就是工廠。

案例

早期我們的 JDBC 操作,註冊驅動時,我們為什麼不使用 DriverManager 的 register 方法,而是采用 Class.forName 的方式?

public class JdbcDemo1 {
 public static void main(String[] args) throws Exception {
 //1.註冊驅動
 //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
 Class.forName("com.mysql.jdbc.Driver");
 //2.獲取連接
 //3.獲取預處理 sql 語句對象
 //4.獲取結果集
 //5.遍歷結果集
 }
}

原因就是:

我們的類依賴瞭數據庫的具體驅動類(MySQL),如果這時候更換瞭數據庫品牌(比如 Oracle),需要修改源碼來重新數據庫驅動。這顯然不是我們想要的。

解決思路:

當是我們講解 jdbc 時,是通過反射來註冊驅動的,代碼如下:

Class.forName(“com.mysql.jdbc.Driver”);//此處隻是一個字符串

此時的好處是,我們的類中不再依賴具體的驅動類,此時就算刪除 mysql 的驅動 jar 包,依然可以編譯(運行就不要想瞭,沒有驅動不可能運行成功的)。同時,也產生瞭一個新的問題,mysql 驅動的全限定類名字符串是在 java 類中寫死的,一旦要改還是要修改源碼。解決這個問題也很簡單,使用配置文件配置。

以上隻是說的解耦的一種思維,方便大傢理解。

解耦的核心思想:若要減少代碼的耦合度,一定要盡量做到各個功能的代碼不能交叉編寫,編寫封閉的代碼。

解耦合

廣大程序猿同胞,經常會看到“解耦合”,也有很多人,會用這個詞來裝X,但是,實際真正能理解的人,並不多。接下來,帶大傢深入淺出的走一遍,如何解耦合。

首先,我們要知道,為什麼要解耦合:通常,我們做一個項目,會用到很多基礎功能塊,比如xxx通信協議,xxxView等等,我們會把這種功能塊封裝成一個庫,如果這個庫,隻能在這個指定的項目運行,這就叫高耦合,這就導致瞭,如果下次再次遇到一個類似的項目,需要用到同樣功能的功能塊時,你會要做很多重復工作。假設,每次使用json時,你都要對json庫進行改造,那將會是一個晴天霹靂。

但是,事與願違,有些情況,還真的不太好解耦。

這裡,我們先舉個栗子,比如排序

一個排序功能,對於大部分比較初級的程序猿來說,可能會寫成這樣:

sort(List<Integer> list)

這樣就導致瞭一個問題所在,這個方法隻能排序int型數據,如果下一個項目,需要用到對String進行排序,那就很尷尬,感覺明明要成功瞭,但是又差一點。對,就是差這一點,就是代碼解耦的關鍵。

我們先要明確,我們需要做的是排序功能,在這個過程中,我們不可避免的需要使用2個數據的大小對比,而這個數據,可能是任何數據,也就是說,排序算法,我們是可以確定下來,做成不動的庫,但是有一個數據大小匹配是我們無法做到的,或者說是庫的耦合點,那怎麼辦呢?

我們就讓使用我們這個功能塊的人,告訴我們就行啦。

下面,我們參考Android庫裡面,有個排序的api

Collections.sort(List<T> list, Comparator<? super T> c);

這裡,Comparator這個接口,就是使用者,需要實現,並且傳遞進去的接口。這樣做,這個排序功能塊就可以應用在任何場合,達到一次開發,受用終身的目的。是不是很神奇?

我們再舉個栗子,socket

我們在開發時,經常會用到socket庫,而socket最常用,最常用的一個功能就是:“連接->發送數據->接收數據->斷開連接->回調結果”

所以,如果需要把這個流程,封裝成一個功能塊是很有意義的。

但是,這裡有一個問題,是阻礙封裝的,就是 “接收數據->斷開連接”,socket讀取數據時,是一個inputStream,是個流,也就是說,其實,你並不知道,數據怎麼樣才算接收 完整/完畢

可能,有的協議,是通過頭2個字節來判斷整個數據長度

可能,有的協議是有幀頭,幀尾,轉義符來判斷整個數據長度

……

這讓我們很頭疼,那怎麼 解決瞭,既然無法知道的東西,就讓應用程序來告訴你唄。和上面一樣,傳入一個協議實現唄:

public interface UnZipDataAction{
// 返回null,表示未接收完全,繼續接收,返回完整的byte[]就認為是已經接收完畢,把結果返回給應用,並且斷開連接
 byte[] getRealData(byte[] recvData); ErrorCode getErrorCode(); }

這樣,我們就把“連接->發送數據->接收數據->斷開連接->回調結果”整個流程封裝成瞭通用的功能塊瞭。

解耦總結來說就是:你能知道的東西就寫死,不知道但是又必須知道的東西,就讓應用程序來告訴你,在java裡面叫接口,在有些語言(OC, swift, C/C++)裡面叫做代碼段。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: