跳出面向?qū)ο笏枷?三) 封裝

原文

簡述

我認為"封裝"的概念在面向?qū)ο笏枷胫惺亲罨A(chǔ)的概念镰吆,它實質(zhì)上是通過將相關(guān)的一堆函數(shù)和一堆對象放在一起秀撇,對外有函數(shù)作為操作通道盈蛮,對內(nèi)則以變量作為操作原料。只留給外部程序員操作方式咖刃,而不暴露具體執(zhí)行細節(jié)。大部分書舉的典型例子就是汽車和燈泡的例子:你不需要知道不同車子的發(fā)動機原理憾筏,只要踩油門就可以跑嚎杨;你不需要知道你的燈泡是那種燈泡,打開開關(guān)就會亮氧腰。我們都會很直覺地認為這種做法非常棒枫浙,是吧?

但是有的時候還是會覺得有哪些地方不對勁古拴,使用面向?qū)ο笳Z言的時候箩帚,我隱約覺得封裝也許并沒有我們直覺中認為的那么好,也就是說黄痪,面向?qū)ο笃鋵嵅]有我們直覺中的那么好膏潮,雖然它已經(jīng)流行了很多很多年。

1. 將數(shù)據(jù)結(jié)構(gòu)和函數(shù)放在一起是否真的合理满力?

函數(shù)就是做事情的焕参,它們有輸入,有執(zhí)行邏輯油额,有輸出叠纷。 數(shù)據(jù)結(jié)構(gòu)就是用來表達數(shù)據(jù)的,要么作為輸入潦嘶,要么作為輸出涩嚣。

兩者本質(zhì)上是屬于完全不同的東西,面向?qū)ο笏枷雽⑺麄兎诺揭黄鸬嘟沟煤瘮?shù)的作用被限制在某一個區(qū)域里航厚,這樣做雖然能夠很好地將操作歸類,但是這種歸類方法是根據(jù)"作用領(lǐng)域"來歸類的锰蓬,在現(xiàn)實世界中可以幔睬,但在程序的世界中,有些不妥芹扭。

不妥的理由有如下幾個:

在并行計算時麻顶,由于執(zhí)行部分和數(shù)據(jù)部分被綁定在一起,這就使得這種方案制約了并行程度舱卡。在為了更好地實現(xiàn)并行的時候辅肾,業(yè)界的工程師們發(fā)現(xiàn)了一個新的思路:函數(shù)式編程。將函數(shù)作為數(shù)據(jù)來使用轮锥,這樣就能保證執(zhí)行的功能在時序上的正確性了矫钓。但你不覺得,只要把數(shù)據(jù)表達和執(zhí)行部分分開,形成流水線新娜,這不就能夠非常方便地將并行數(shù)提高了么赵辕?

我來舉個例子: 在數(shù)據(jù)和函數(shù)沒有分開時,程序的執(zhí)行流程是這樣:

A.function2() -> A.function3()     最后得到經(jīng)過處理的A

當處于并發(fā)環(huán)境時杯活,假設(shè)有這么多任務同時到達

A.f1() -> A.f2() -> A.f3()     最后得到經(jīng)過處理的A
B.f1() -> B.f2() -> B.f3()     最后得到經(jīng)過處理的B
C.f1() -> C.f2() -> C.f3()     最后得到經(jīng)過處理的C
D.f1() -> D.f2() -> D.f3()     最后得到經(jīng)過處理的D
E.f1() -> E.f2() -> E.f3()     最后得到經(jīng)過處理的E
F.f1() -> F.f2() -> F.f3()     最后得到經(jīng)過處理的F
...

假設(shè)并發(fā)數(shù)是3匆帚,那么完成上面類似的很多個任務熬词,時序就是這樣

| time | 1   | 2   | 3   | 4   | 5   | 6   | 7   | 8   | 9   | 10  | 11  | 12  |
|------|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
| A    | A.1 | A.2 | A.3 |     |     |     |     |     |     |     |     |     |
| B    | B.1 | B.2 | B.3 |     |     |     |     |     |     |     |     |     |
| C    | C.1 | C.2 | C.3 |     |     |     |     |     |     |     |     |     |
| D    |     |     |     | D.1 | D.2 | D.3 |     |     |     |     |     |     |
| E    |     |     |     | E.1 | E.2 | E.3 |     |     |     |     |     |     |
| F    |     |     |     | F.1 | F.2 | F.3 |     |     |     |     |     |     |
| G    |     |     |     |     |     |     | G.1 | G.2 | G.3 |     |     |     |
| H    |     |     |     |     |     |     | H.1 | H.2 | H.3 |     |     |     |
| I    |     |     |     |     |     |     | I.2 | I.2 | I.3 |     |     |     |
| J    |     |     |     |     |     |     |     |     |     | J.1 | J.2 | J.3 |
| K    |     |     |     |     |     |     |     |     |     | K.1 | K.2 | K.3 |
| L    |     |     |     |     |     |     |     |     |     | L.1 | L.2 | L.3 |

當數(shù)據(jù)和函數(shù)分開時旁钧,并發(fā)數(shù)同樣是3,就能形成流水線了互拾,有沒有發(fā)現(xiàn)吞吐量一下子上來了歪今?

| time | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12|
|------|---|---|---|---|---|---|---|---|---|---|---|---|
| f1() | A | B | C | D | E | F | G | H | I | J | K | L |
| f2() | Z | A | B | C | D | E | F | G | H | I | J | K |
| f3() | Y | Z | A | B | C | D | E | F | G | H | I | J |

你要是粗看一下,誒颜矿?怎么到了第13個周期K才剛剛結(jié)束寄猩?上面一種方案在第12個周期的時候就結(jié)束了?不能這么看的哦骑疆,其實在12個周期里面田篇,Y、Z也已經(jīng)交付了箍铭。因為流水線吞吐量的提升是有過程的泊柬,我截取的片段應該是機器在持續(xù)運算過程中的一個片段。

我們不能單純地去看ABCD诈火,要看交付的任務數(shù)量兽赁。在12個周期里面,大家都能夠完成12個任務冷守,在11個周期里面刀崖,流水線完成了11個任務,前面一種只完成了9個任務拍摇,流水線的優(yōu)勢在這里就體現(xiàn)出來了:每個時間段都能穩(wěn)定地交付任務亮钦,吞吐量很大。而且并發(fā)數(shù)越多充活,跟第一種方案比起來的優(yōu)勢就越大或悲,具體的大家也可以通過畫圖來驗證。

數(shù)據(jù)部分就是數(shù)據(jù)部分堪唐,執(zhí)行部分就是執(zhí)行部分巡语,不同類的東西放在一起是不合適的

函數(shù)就是一個執(zhí)行黑盒,只要滿足函數(shù)調(diào)用的充要條件(給夠參數(shù))淮菠,就是能夠確定輸出結(jié)果的男公。面向?qū)ο笏枷雽⒑瘮?shù)和數(shù)據(jù)綁在一起,這樣的封裝擴大了代碼重用時的粒度。如果將函數(shù)和數(shù)據(jù)拆開枢赔,代碼重用的基本元素就由對象變?yōu)榱撕瘮?shù)澄阳,這樣才能更靈活更方便地進行代碼重用。

嗯踏拜,誰都經(jīng)歷過重用對象時碎赢,要把這個對象所依賴的所有東西都要移過來,哪怕你想用的只是這個對象里的一個方法速梗,然而很有可能你的這些依賴是跟你所需要的方法無關(guān)的肮塞。

但如果是函數(shù)的話,由于函數(shù)自身已經(jīng)是天然完美封裝的了姻锁,所以如果你要用到這個函數(shù)枕赵,那么這個函數(shù)所有的依賴你都需要,這才是合理的位隶。

2. 是否所有的東西都需要對象化拷窜?

面向?qū)ο笳Z言一直以自己做到"一切皆對象"為榮,但事實是:是否所有的東西都需要對象化涧黄?

在iOS開發(fā)中篮昧,有一個類叫做NSNumber,它封裝了所有數(shù)值:double笋妥,float懊昨,unsigned int, int...等等類型,在使用的時候它弱化了數(shù)值的類型挽鞠,使得非常方便疚颊。但問題也來了,計算的時候是不能直接對這個對象做運算的信认,你得把它們拆成數(shù)值材义,然后進行運算,然后再把結(jié)果變成NSNumber對象嫁赏,然后返回其掂。這是第一點不合理。第二點不合理的地方在于潦蝇,運算的時候你不知道原始數(shù)據(jù)的類型是什么款熬,拆箱裝箱過程中難免會導致內(nèi)存的浪費(比如原來uint8_t的數(shù)據(jù)變成unsigned int),這也十分沒有必要攘乒。

還有就是我們的file descriptor贤牛,它本身是一個資源的標識號,如果將資源抽象成對象则酝,那么不可避免的就會使得這個對象變得非常龐大殉簸,資源有非常多的用法,你需要將這些函數(shù)都放到對象里去。在真正傳遞資源的時候般卑,其實我們也只是關(guān)心資源標識而已武鲁,其它的真的無需關(guān)心。

我們已經(jīng)有函數(shù)作為黑盒了蝠检,拿著數(shù)據(jù)塞到黑盒里就夠了沐鼠。

3. 類型爆炸

由于數(shù)據(jù)和函數(shù)綁定到了一起,在邏輯上有派生關(guān)系的兩種對象往往可以當作一種叹谁,以派生鏈最上端的那個對象為準饲梭。單純地看這個現(xiàn)象直覺上會覺得非常棒,父親有的兒子都有本慕。但在實際工程中排拷,派生是非常不好控制的侧漓,它導致同一類類型在工程中泛濫:ViewController锅尘、AViewController、BViewController布蔗、ThisViewController藤违、ThatViewController...

你有沒有發(fā)現(xiàn),一旦把執(zhí)行和數(shù)據(jù)拆解開纵揍,就不需要這么多ViewController了顿乒,派生只是給對象添加屬性和方法。但事實上是這樣:

struct A {              Class A extends B
    struct B b;         {
    int number;             int number;
}                       {

前者和后者的相同點是:在內(nèi)存中泽谨,它們的數(shù)值部分的布局是一模一樣的璧榄。不同點是:前者更強烈地表達了組合,后者更強烈地表達的是繼承吧雹。然而我們都知道一個常識:組合要比繼承更加合適骨杂,這在我這一系列的第一篇文章中有提到。

上兩者的表達在內(nèi)存中沒有任何不同雄卷,但在實際開發(fā)階段中搓蚪,后者會更容易把項目引入一個壞方向。

總結(jié)

為什么面向?qū)ο髸绱肆餍卸○模课蚁肓艘幌聵I(yè)界關(guān)于這個談論的最多的是以下幾點:

  1. 它能夠非常好地進行代碼復用
  2. 它能夠非常方便地應對復雜代碼
  3. 在進行程序設(shè)計時妒潭,面向?qū)ο蟾臃铣绦騿T的直覺

第一點在理論上確實成立,但實際上大家都懂揣钦,在面向?qū)ο蟮拇蟊尘跋脉ㄔ郑瑢懸欢伪阌趶陀玫拇a比面向過程背景下難多了。關(guān)于第二點冯凹,你不覺得正是面向?qū)ο蠡涯叮虐压こ套儚碗s的么?如果層次清晰,調(diào)用規(guī)范团驱,無論面向?qū)ο筮€是面向過程摸吠,處理復雜業(yè)務都是一樣好,等真的到了非常復雜的時候嚎花,對象間錯綜復雜的關(guān)系只會讓你處理起來更加頭疼寸痢,不如面向過程來得簡潔。關(guān)于第三點紊选,這其實是一個障眼法啼止,因為無論面向什么的設(shè)計,最終落實下來兵罢,還是要面向過程的献烦,面向?qū)ο笾皇窃谔幚碚{(diào)用關(guān)系時符合直覺,在架構(gòu)設(shè)計時卖词,理清需求是第一步巩那,理清調(diào)用關(guān)系是第二步,理清實現(xiàn)過程是第三步此蜈。面向?qū)ο笞屇阍诘诙綍r就產(chǎn)生了設(shè)計完成的錯覺即横,只有再往下落地到實現(xiàn)過程的時候,你才會發(fā)現(xiàn)第二步中都有哪些錯誤裆赵。

所以綜上所述东囚,我的觀點是:面向?qū)ο笫窃诩軜?gòu)設(shè)計時非常好的思想,但如果只是簡單映射到程序?qū)崿F(xiàn)上來战授,引入的缺點會讓我們得不償失页藻。


后記

距離上一次博文更新已經(jīng)快要一個月了,不是我偷懶植兰,實在是太忙份帐,現(xiàn)在終于有時間可以把"跳出面向?qū)ο?系列完成了。針對面向?qū)ο蟮?個支柱概念我寫了三篇文章來挑它的刺钉跷,看上去有一種全盤否定的感覺弥鹦,而我倒不至于希望大家回去下一個項目就開始面向過程的開發(fā),我希望大家能夠針對這一系列文章提出的面向?qū)ο蟮谋锥艘蓿瑖栏褚?guī)范代碼的行為彬坏,知道哪些可行哪些不可行。過去的工作中我深受其苦膝晾,往往沒有時間去詳細解釋為什么這么直覺的東西實際上不可行栓始,要想解釋這些東西就得需要各種長篇大論。最痛苦的是血当,即便長篇大論說完了幻赚,最后對方還無法理解禀忆,照樣寫出垃圾代碼出來害人。

現(xiàn)在好了落恼,長篇大論落在紙上了箩退,說的時候聽不懂,回去總可以翻文章慢慢理解了吧佳谦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戴涝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钻蔑,更是在濱河造成了極大的恐慌啥刻,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咪笑,死亡現(xiàn)場離奇詭異可帽,居然都是意外死亡,警方通過查閱死者的電腦和手機窗怒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門映跟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兜粘,你說我怎么就攤上這事申窘⊥溲粒” “怎么了孔轴?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碎捺。 經(jīng)常有香客問我路鹰,道長,這世上最難降的妖魔是什么收厨? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任晋柱,我火速辦了婚禮,結(jié)果婚禮上诵叁,老公的妹妹穿的比我還像新娘雁竞。我一直安慰自己,他們只是感情好拧额,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布碑诉。 她就那樣靜靜地躺著,像睡著了一般侥锦。 火紅的嫁衣襯著肌膚如雪进栽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天恭垦,我揣著相機與錄音快毛,去河邊找鬼格嗅。 笑死,一個胖子當著我的面吹牛唠帝,可吹牛的內(nèi)容都是我干的屯掖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼襟衰,長吁一口氣:“原來是場噩夢啊……” “哼懂扼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起右蒲,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阀湿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瑰妄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陷嘴,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年间坐,在試婚紗的時候發(fā)現(xiàn)自己被綠了灾挨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡竹宋,死狀恐怖劳澄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜈七,我是刑警寧澤秒拔,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站飒硅,受9級特大地震影響砂缩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜三娩,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一噩咪、第九天 我趴在偏房一處隱蔽的房頂上張望袍睡。 院中可真熱鬧芽卿,春花似錦在塔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至回官,卻和暖如春曹宴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歉提。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工笛坦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留区转,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓版扩,卻偏偏與公主長得像废离,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子礁芦,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容