封裝與依賴隔離

更新於 2024/01/27閱讀時間約 7 分鐘

物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。


一般的模組系統就是把相關的程式碼蒐集起來放在一起,並根據粒度組織成階層結構,越內層的模組越細緻複雜且功能越多。一般會根據任務把相關的程式碼組成一個模組,例如連線相關的模組、數值計算相關的模組、資料庫管理相關的模組等,如此要找程式碼時就比較容易。更細緻的模組則會作為子模組放在相應的模組裡,例如連線相關的模組可能包含socket的模組、http protocol的模組等。這種透過階層結構管理程式碼的方式就是模組化設計。模組化設計透過組織相關聯的程式碼將知識連結起來,讓它們形成一個概括的概念,並透過模組的階層關係把更複雜的結構分解成較小的相對分離的部分,使得知識更容易學習與理解。物件導向把這種模組化的思維應用在物件上,與此物件所表達的概念相關的操作就被實作成它的方法。這也使得物件常常被用做功能導向的管理器,例如ConnectionManager。相對地,trait/typeclass只有在編譯機制上把函式與類型關聯起來,它們的實作仍可以擺在任何地方,唯有透過模組系統,才能有效的把相關的程式碼組織起來。


模組還有控制存取權的功能,也就是決定誰可以存取哪個函式或是類型,這讓它具有封裝的能力。透過存取權的控制,我們可以保證某些不安全的函式或成員變數只能被特定位置的函式使用,因此讓一些非法的情境實質上不可能發生。例如我們必須限制類別class Interval { float min; float max; }的成員的存取,因為它們必須符合min <= max的限制,應提供其他方法安全地存取它們。這種方式可以用來保證資料的完整性,它相較於利用代數資料類型更具彈性,事實上物件導向傾向使用這種方式而非代數資料類型,這可以稱作是對資料完整性的封裝。這種存取權的控制可以讓一些較複雜的操作不會暴露在外面,畢竟它們不能被存取,當我們在外面時就不需要理解它在幹嘛,因而做到對複雜度的封裝。它也能用來控制函式之間的依賴關係,讓內部的功能強大且情境複雜的函式不會被外部使用,從而降低誤用函式的風險,並減少修改此函式所需考慮的問題。而最外層的存取權一般用作函式庫的介面,它不能隨意更改,否則會產生未知的影響範圍。


模組控制存取權的方法是藉由定義一個包括函式、類型、介面等實體的範圍,並選擇要公開哪個實體,範圍外部的實體只能使用已公開的實體。這種控制存取權的功能與資訊安全無關,它是用來控制實體間的依賴關係,只要一個實體被公開,它就有可能被外部依賴,因此我們不能隨意修改這個實體的承諾。反之,透過限制可能的依賴關係與範圍,可以讓我們減低一些顧慮。同樣的概念也可以應用到函式裡,我們可以藉由限制變數的作用域,防止變數被其他地方的程式碼誤用,並讓我們可以專注在較小的範圍裡。例如當我們需要較複雜的方法準備一個值傳給函式時,與其寫成let a = …; let b = …; let param = prepare(a, b);,不如把a, b限制在範圍內:let param; { let a = …; let b = …; param = prepare(a, b); },如果是rust則可以寫成let param = { let a = …; let b = …; prepare(a, b) }以避免變數修改。我們也可以藉由直接抽取成函式做到一樣的結果,但它還需要將範圍內所使用的外部變數捕捉起來,並把需要輸出的變數回傳到範圍外。


有些人建議應該要把複雜函式的每個步驟都拆解成多個小函式以減少縮排,但這樣做會讓每個小函式都暴露在外面,如果它所在的模組或類型很大的話,就會造成一定程度的困擾。這這種基於實作且偏向特定情境的函式本來就應該限制在非常小的作用域裡,就像上面的準備參數值的例子一樣。我把這種存取範圍超出預期的狀況稱為「依賴洩漏」,定義函式時應該在意函式可以被誰存取,這會決定函式的重要性與使用情境,不合適的存取範圍會讓實體之間的依賴關係被污染。相反地,我們可以藉由「依賴隔離」解決這個問題。我們可以藉由模組的存取權控制把小函式限縮在一個範圍,並只開放組合起來的函式給外部使用。然而這只適用於像是rust的mod或是ts的namespace這類的小範圍模組系統,否則像是python這種依賴於檔案系統的模組系統,這種做法就必須把這個函式的定義獨立成一個檔案,這非常冗余。另外一種方法是藉由定義區域函式以限制它的作用域,也就是直接把小函式放在被分解的函式的定義範圍裡,這同樣可以達到隔離依賴範圍的目的。在Haskell這種特別依賴高階函式的程式語言中,很常會需要定義輔助函式作為參數,因此它特別提供where語句定義區域函式。更廣義的,在設計介面/特性時也應該要考慮方法之間的依賴關係,如果它包含太多方法,就會造成依賴此介面的實體依賴太多不必要的方法,因此應該盡量把方法分離成較小的介面,這稱做「介面隔離」。Iterator pattern或是pipeline pattern本質上也受惠於依賴隔離,它們透過把計算過程分解成各個簡單且獨立的部分,讓整體的邏輯變得容易理解。如果用迴圈寫一樣的邏輯,會發現當我們在閱讀時必須時刻記住這個變數是什麼意思,因為後面的程式碼有可能會用到它,而Iterator pattern把這類的依賴關係隔離開來,讓我們更能專注在每一步操作本身的意義。


控制存取權一般有兩種形式,一個是在定義函式、類型等實體時宣告是否公開,另一種則是統一在一個地方羅列需要公開的實體。像是物件導向的public/private、rust的pub、TypeScript的export就是第一種形式,而c++的標頭檔、Haskell的export list和ocaml的sig/mli則是第二種形式。第一種形式比較容易閱讀理解,而第二種讓我們必須把同樣的實體寫兩遍並放在不同地方,如果沒有language server輔助會很麻煩,而且會讓判斷實體的依賴範圍變得不直接。模組裡函式、類型的存取範圍控制應該寫在宣告附近,不同的範圍會影響它的意義與重要性,因而改變我們對這些實體的態度。


在物件導向裡,類別的封裝除了把存取控制邊界限制在物件本身,還包含一種特殊的控制方式。它可以宣告方法為protected,讓外部實體無法存取,但繼承此類別的子類別可以存取。這讓類別具有感染性,如果一個函式需要使用這個方法就只能加到這個類別上。這種特殊的控制權限非常微妙,一個方法是否該宣告成protected很難判斷,你怎麼知道只有它的子類會用到這個方法,就算是真的有必要這麼做嗎。如果你把一些關鍵方法宣告成protected,可能會造成未來在實作一些函式時也必須塞到這個類別裡面,因而讓這個類別過度膨脹,破壞單一職責的原則。事實上宣告成protected的目的常常是為了保證資料完整性,讓外部實體無法隨意修改資料或使用危險的方法,但又希望它能被子類使用或改寫。最好的方法應該是把它實作成函式,並在一個模組內就把問題處理掉並封裝起來。

avatar-img
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相
類似於trait/typeclass的特性系統能提供程式「延展性」,它能讓函式針對不同的類型做出不同的行為。這種機制與物件導向的繼承非常像,然而特性系統的彈性比較大一點,而且概念上也有一些差別。為了探討討論這些差異,我們必須深入了解繼承機制到底是什麼。 繼承並不是建立子類關係的唯一方法。所謂的
所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
前幾篇文章在討論類型時,只討論了乘法與加法類型,這只是最基礎的類型構造方式,另外還有函式類型和泛型等概念還沒討論。在討論函式的類型之前,必須先討論函式的正確用法。對於程序式編程來說,函式是一段可重複使用的執行代碼,輸入的參數是用來控制執行行為的,因此比起函式(function)更應該稱它為程序(pr
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相
類似於trait/typeclass的特性系統能提供程式「延展性」,它能讓函式針對不同的類型做出不同的行為。這種機制與物件導向的繼承非常像,然而特性系統的彈性比較大一點,而且概念上也有一些差別。為了探討討論這些差異,我們必須深入了解繼承機制到底是什麼。 繼承並不是建立子類關係的唯一方法。所謂的
所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
前幾篇文章在討論類型時,只討論了乘法與加法類型,這只是最基礎的類型構造方式,另外還有函式類型和泛型等概念還沒討論。在討論函式的類型之前,必須先討論函式的正確用法。對於程序式編程來說,函式是一段可重複使用的執行代碼,輸入的參數是用來控制執行行為的,因此比起函式(function)更應該稱它為程序(pr
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
從2022年9月疫情後開放國外觀光客來台,全台各地紛紛提早點亮聖誕樹,其實在台北,每年一到11、12月,就是拍花展與聖誕燈飾的熱門旺季,2023年又算是疫情後的第一年全年開放國外旅客來台觀光,全台各地方政府、百貨飯店更是卯足了勁,紛紛提早點亮聖誕樹,希望吸引更多國內外觀光客前來旅遊。
Thumbnail
台積電成熟製程降價,IC設計成本降低 雖然有中國大擴產因素,但也表市場需求疲弱不振,選舉行情中都被刻意忽視 晶圓代工成熟製程業者,面臨產能利用率六成保衛戰。傳出聯電、世界先進及力積電等指標廠為搶救產能利用率,大砍明年首季報價,專案客戶降幅更高達15%至20%,藉此「削價換量」,幅度是疫後最大
Thumbnail
第32章 上海從未封城 5月29日,周日。 五月倒數第三天,也是五月最後完整一周的最後一天。 大家都很期待那天的新聞發佈會,希望會上能宣佈點什麼。 上海的上午10點,他這裡的下午2點,他跟S一起看直播。雖然需要縱跨太平洋,直播還是能看的,只不過有點延遲。 一開始主持人介紹出席的4個人,有市衛健委副主
在這個每個人都渴望展現自我風格的時代,有什麼比打造一個屬於自己的獨特空間更能實現這個願望呢?而我們,就是為你實現這個夢想的最佳夥伴。 我們相信,裝潢設計並非僅止於選擇色彩、材料和風格,而是如何將每個空間轉變為一部講述主人獨特故事的藝術品。
Thumbnail
第13章 無限續杯的封城 3月28日,週一。 早上,他起床,一邊刷牙一邊刷微博,才發現幾個小時前上海人都經歷了什麼。浦東沒有封控的社區,人們傾巢出動,在深沉夜色中,沖去超市、菜場,搶購食物。有些已經封了的社區,居委會放人出去2個小時,有的社區更人性點,放3個小時。反正都是卡著一個時間點,過了線,你們
Thumbnail
第2章 封鎖的日子 這個南太平的島國,國土主要是南北兩個大島。人口500萬左右,差不多是上海人口的五分之一,但面積是上海的四十多倍。 去年,2021年,八月中旬,冬天將近結尾時,位於北島、有160萬人口的第一大城市,確診了1個新冠病例,是近期首個隔離點之外的新病例。 於是,第二天,8月17日,週二,
Thumbnail
——那天以後,偶爾,會不經心地碰觸合成器和鋼琴的鍵盤, 像寫日記一般錄下草圖(sketch)。 從其中選了12首中意的草圖作成專輯。 不多加施予,敢於素然呈現。 今後直到精疲力盡為止,都會這樣持續記錄「日記」吧。 坂本龍一 (譯自 Ryuichi Sakamoto 12 官方網站)
Thumbnail
烏黑的燕子在雨後緩緩飛來,被雨水澆濕的翅膀,就像一名紳士的燕尾服,需要好好地整理。於是停留在電線上,細心而毫不猴急地整理一根根珍貴的羽翼。
Thumbnail
大部分人在學習投資時容易犯下一個錯誤,那就是只問報酬不問風險,缺乏風險的認知是新手最容易犯下的錯誤之一。 許多人都聽過【高報酬=高風險】或者是【想要高報酬就必須承擔更高的風險】這類的說法,在我看來,這樣的說法並不完全正確。因為並不是只要承受更高的風險,就一定會有好報酬......
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
從2022年9月疫情後開放國外觀光客來台,全台各地紛紛提早點亮聖誕樹,其實在台北,每年一到11、12月,就是拍花展與聖誕燈飾的熱門旺季,2023年又算是疫情後的第一年全年開放國外旅客來台觀光,全台各地方政府、百貨飯店更是卯足了勁,紛紛提早點亮聖誕樹,希望吸引更多國內外觀光客前來旅遊。
Thumbnail
台積電成熟製程降價,IC設計成本降低 雖然有中國大擴產因素,但也表市場需求疲弱不振,選舉行情中都被刻意忽視 晶圓代工成熟製程業者,面臨產能利用率六成保衛戰。傳出聯電、世界先進及力積電等指標廠為搶救產能利用率,大砍明年首季報價,專案客戶降幅更高達15%至20%,藉此「削價換量」,幅度是疫後最大
Thumbnail
第32章 上海從未封城 5月29日,周日。 五月倒數第三天,也是五月最後完整一周的最後一天。 大家都很期待那天的新聞發佈會,希望會上能宣佈點什麼。 上海的上午10點,他這裡的下午2點,他跟S一起看直播。雖然需要縱跨太平洋,直播還是能看的,只不過有點延遲。 一開始主持人介紹出席的4個人,有市衛健委副主
在這個每個人都渴望展現自我風格的時代,有什麼比打造一個屬於自己的獨特空間更能實現這個願望呢?而我們,就是為你實現這個夢想的最佳夥伴。 我們相信,裝潢設計並非僅止於選擇色彩、材料和風格,而是如何將每個空間轉變為一部講述主人獨特故事的藝術品。
Thumbnail
第13章 無限續杯的封城 3月28日,週一。 早上,他起床,一邊刷牙一邊刷微博,才發現幾個小時前上海人都經歷了什麼。浦東沒有封控的社區,人們傾巢出動,在深沉夜色中,沖去超市、菜場,搶購食物。有些已經封了的社區,居委會放人出去2個小時,有的社區更人性點,放3個小時。反正都是卡著一個時間點,過了線,你們
Thumbnail
第2章 封鎖的日子 這個南太平的島國,國土主要是南北兩個大島。人口500萬左右,差不多是上海人口的五分之一,但面積是上海的四十多倍。 去年,2021年,八月中旬,冬天將近結尾時,位於北島、有160萬人口的第一大城市,確診了1個新冠病例,是近期首個隔離點之外的新病例。 於是,第二天,8月17日,週二,
Thumbnail
——那天以後,偶爾,會不經心地碰觸合成器和鋼琴的鍵盤, 像寫日記一般錄下草圖(sketch)。 從其中選了12首中意的草圖作成專輯。 不多加施予,敢於素然呈現。 今後直到精疲力盡為止,都會這樣持續記錄「日記」吧。 坂本龍一 (譯自 Ryuichi Sakamoto 12 官方網站)
Thumbnail
烏黑的燕子在雨後緩緩飛來,被雨水澆濕的翅膀,就像一名紳士的燕尾服,需要好好地整理。於是停留在電線上,細心而毫不猴急地整理一根根珍貴的羽翼。
Thumbnail
大部分人在學習投資時容易犯下一個錯誤,那就是只問報酬不問風險,缺乏風險的認知是新手最容易犯下的錯誤之一。 許多人都聽過【高報酬=高風險】或者是【想要高報酬就必須承擔更高的風險】這類的說法,在我看來,這樣的說法並不完全正確。因為並不是只要承受更高的風險,就一定會有好報酬......