剪斷枷鎖——延遲綁定
來自專欄編程方法論
前言
延遲綁定是一個編程裡面的一個技巧,同樣也是一種編程的思想。比較有經驗的老司機肯定個定會根據經驗寫出正確的代碼,但是新人們卻很容易掉入這樣的坑。最近我們交流群(QQ群 745478917)裡面的小子狐就遇到了這樣的問題,並且我卻發現很難用一兩句話講清楚,所我就想寫一篇文章記錄一下我們這個問題,同時也給一些其他新人能夠有一個參考。
問題是怎麼出現的
那天小子狐突然玩我一個問題,說是自己要在一個 Node 模塊裡面導出一個 mssql 的 connection,要在其他 express 模塊裡面用,比如下面這樣:
// connection.js// 我沒用過 mssql 這裡只是簡單示意module.exports = mssql.connect(xxxx:xxxx) // 導出一個 mssql 的連接
希望在下面這個模塊裡面用:
// moduel.jsconst connection = require(./connection)module.exports = function middleware(req, res) { connection.query()}
這對於同步 I/O 的 API 來說是沒有問題的,但是對於非同步的 API ,尤其是在 JavaScript 這種充斥這非同步的語言中該怎麼辦呢?
先講講模塊
大家想一想就會發現,我們上面那種情況只會在 JavaScript / Python 這類動態才會出現,而像 C / C++ / Java 這類語言則不會出現,為什麼呢?原因在於動態語言的一個文件或者一個模塊,在被引入的時候是靜態的不會執行的,而 JavaScript 中 require 一個模塊則需要執行代。
動態語言這麼做有有弊,利是可以在運行時動態 require 模塊,常見的就是 webpack 載入 loader 的時候就是利用了這種技術。當然弊端也是有的,那就是意識不到位的話,就會把模塊簡單當成「一段代碼分成幾段」來用,就像上一節中所說的一樣。
所以要怎麼樣做呢?我的建議是除了入口文件以外,其他文件(模塊)只聲明類或者函數,亦或者變數,但是最好不要執行具體的業務代碼,以上一節的 connection.js 的代碼為例,我們可以這麼做:
// module.js// 我沒用過 mssql 這裡只是簡單示意module.exports = async function createConnection() { return await mssql.connect(xxxx:xxxx) }
那麼接下里問題又來了,那麼我不可能在每一個需要 connection 的模塊都 createConnection 一次,這明顯有問題吧?沒錯,但是我們為什麼在其他地方需要依賴 connection.js 這個文件呢?我們只需要一個 connection 名字,就能可以了呀。
用函數的參數來延遲綁定
我們定義個函數 f,參數為 x,那在定義這函數的時候 x 只是一個名字,並不代表任何值。同樣的,我們在定義個模塊的時候,我們可不可以也就以來一個 connection 的名字,而不要求這個 connection 有具體的指代呢?
其實這時候我們只需要把生成 middleware 的過程包裝成函數,然後用一個 connection 參數來延遲這個 connection 名字和具體的對象綁定。比如上面你的 Node 的 module.js 可以寫成如下這樣:
// module.jsmoduel.exports = function createMiddleware(connection) { return function middleware(req, res) { connection.query() }}
然後在具體調用 createMiddleware 的時候再綁定 connection:
// main.jsasync function main() { const createMiddleware = require(./module) const connection = await require(./connection)(xxx:xxx) app.use(createMiddleware(connection))}main()
這樣子我們就把 middleware 和具體的 connection 對的綁定延遲到了具體 create 的時候,而在我們聲明(定義)模塊的時候以來的 connection 只是一個名字。(如果不能理解,查看函數形參和實參的相關資料)
利用 Context 延遲綁定
我們上面那種寫法雖然能解決問題,但是熟悉 express 的大家一定更熟悉的用 context (上下文)來綁定對象,這在動態語言裡面非常方便,各種 express 中間件也在利用 context 來傳遞對象。
// module.jsmoduel.exports = function middleware(req, res) { req.connection.query()}
用一個中間件來將 connection 綁定在 req 對象上面。
// main.jsasync function main() { const middleware = require(./module) const connection = await require(./connection)(xxx:xxx) app.use(req => { req.connection = connection }) // 將 connection 綁定在 req 的上下文里。 app.use(middlware)}main()
寫在最後
我這篇文章只講了動態語言的延遲綁定,其實在非動態語言裡面延遲綁定,靜態語言裡面延遲綁定會稍難一些,比如在 C 裡面可以通過指針(函數指針),比如 C++ 的 virtual table 等等。無論用什麼樣技術和實現,其思想都是一樣的,都是將我們從對一個「具象」的依賴中脫身出來,從而迎接「抽象」。
如果喜歡我們的文章,歡迎關注我們的公眾號 「代碼律動codingwave」 和 QQ群 745478917
Hello World!
推薦閱讀:
※C++中關於跨平台中子線程式控制制的一些心得(2):用於線程的同步的Async容器
※Leetcodes Solutions 24 Swap Nodes in Pairs
※假如你可以個人主導 C++,你打算怎樣裁剪、擴充和訂製 C++ 來達到你心中最完美的 C++?
※Leetcode Solutions(一) two-sum
※最為經典的計算機編程語言之一 C語言