為了展示緊密耦合的相依性有什麼後果、又該如何拆解,我們來看連接兩套系統的幾種不同選項。
情境:我們要建一個線上銀行系統,讓客戶能從其他銀行把錢存入自己的帳戶。前端 Web 應用必須與後端管理資金轉帳的財務系統整合。
最簡單的作法:直接開 socket#
最容易的連法是走 TCP/IP。過去十五年來但凡有點自尊的作業系統或程式庫都內建 TCP/IP 堆疊;它是把資料在網際網路與區域網路上數百萬台電腦之間搬運的通用協定。既然如此,為什麼不直接用最普及的網路協定來讓兩套應用程式溝通?
假設那個遠端存款函式只吃兩個參數:人名與美元金額。以下幾行 C# 就夠了(用 C 或 Java 寫幾乎一模一樣):
String hostName = "www.eaipatterns.com";
int port = 80;
IPHostEntry hostInfo = Dns.GetHostByName(hostName);
IPAddress address = hostInfo.AddressList[0];
IPEndPoint endpoint = new IPEndPoint(address, port);
Socket socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(endpoint);
byte[] amount = BitConverter.GetBytes(1000);
byte[] name = Encoding.ASCII.GetBytes("Joe");
int bytesSent = socket.Send(amount);
bytesSent += socket.Send(name);
socket.Close();不用昂貴的中介軟體、不用 EAI 工具、不用 RPC 工具組,就十行程式碼。跑起來它告訴我們:「已送出 7 個位元組」。瞧!整合哪有那麼難?
四個致命問題#
平台技術:位元組流不是通用格式#
TCP/IP 的強項是支援廣泛,幾乎能連上網路上任何一台電腦。但這種平台獨立性只對最簡單的訊息成立:位元組流。
程式碼用 BitConverter 把資料轉成位元組陣列,它依據資料型別在記憶體中的內部表示法來轉換——而整數的內部表示法因系統而異。.NET 用 32 位元整數,其他系統可能用 64 位元。範例送出 4 個位元組代表一個 32 位元整數;一個用 64 位元的系統會傾向從網路讀 8 個位元組,結果把整則訊息(連客戶姓名一起)解讀成單一個數字。
位元組順序(endianness)更麻煩。大端序(big-endian)先存最高位元組,小端序(little-endian)先存最低位元組。PC 走小端序,所以這段程式碼實際送出的是:
232 3 0 0- 小端序解讀:232 + 3 × 2⁸ = 1000
- 大端序解讀:232 × 2²⁴ + 3 × 2¹⁶ = 3,892,510,720
Joe 要變成大富翁了。這個作法只在「所有連線電腦以相同內部格式表示數字」的假設下才成立。
位置:機器位址被寫死#
程式碼直接指定了遠端機器的位置(www.eaipatterns.com)。DNS 在網域名稱與 IP 之間給了一層間接,但——
- 若要把功能搬到另一個網域的另一台機器呢?
- 若機器掛了、必須另起一台呢?
- 若要把資訊同時送到多台機器呢?
每一種情境都得改程式碼。用到的遠端函式一多,這會變得極度繁瑣。
時間:雙方必須同時在線#
TCP/IP 是連線導向協定,傳資料前得先建立連線,而建立 TCP 連線需要 IP 封包在收發兩端之間來回。這要求兩台機器與網路三者同時可用;其中任何一項故障或因高負載而不可用,資料就送不出去。
資料格式:契約寫死在兩端#
這段通訊依賴極其嚴格的資料格式:4 個位元組的金額,接著一串代表客戶帳號的字元。若想插入第三個參數(例如幣別名稱),收發雙方都得改成新格式。
耦合的四個維度#
這個極簡整合方案又快又便宜,卻極度脆弱——因為參與的雙方對彼此做了以下假設:
- 平台技術——數字與物件的內部表示法
- 位置——寫死的機器位址
- 時間——所有元件必須同時可用
- 資料格式——參數清單與型別必須完全吻合
耦合正是「雙方溝通時對彼此所做假設的多寡」。這個方案要求的假設很多,所以它是緊密耦合的。

圖 1-7:緊密耦合的互動
逐一拆掉相依性#
要讓方案更鬆散耦合,就把這些相依性一個一個拿掉:
- 改用自我描述、平台獨立的標準資料格式(例如 XML),解掉平台技術相依。
- 不要把資訊直接送到特定機器,而是送到可定址的「通道(channel)」。通道是一個邏輯位址,收發雙方都同意這個通道,卻不必知道對方的身分——解掉位置相依。
- 讓通道把送出的請求排隊,直到網路與接收系統就緒——解掉時間相依。要讓通道能排隊,就得把資料包成自我完備的訊息(message),通道才知道一次該緩衝與遞送多少資料。
- 允許通道內部做資料格式轉換——解掉資料格式相依。某套系統格式改了,只需改轉換器,其他參與系統不受影響;當很多應用程式送資料到同一個通道時,這特別有價值。

圖 1-8:鬆散耦合的互動
代價與取捨#
共同資料格式、排隊通道與轉換器這些機制,把緊密耦合的方案變成鬆散耦合的方案。寄件端不再依賴收件端的內部資料格式與位置,甚至不必在意對方此刻是否準備好接受請求——移除這些相依性讓整體方案更能容忍變動,這正是鬆散耦合的關鍵好處。
主要缺點是額外的複雜度:這已經不是十行程式碼能搞定的事了。因此我們改用訊息導向中介軟體來提供這些服務,讓鬆散耦合的資料交換幾乎和一開始那個範例一樣容易。
鬆散耦合是萬靈丹嗎?和企業架構中的一切一樣,沒有唯一的最佳解。它帶來彈性與可擴展性這些重要好處,但也引入更複雜的程式模型,讓設計、建構與除錯變得更困難。