跳出面向?qū)ο笏枷?一) 繼承

原文

簡(jiǎn)述


我會(huì)在這篇這一系列文章中談?wù)劽嫦驅(qū)ο笏枷氲膸讉€(gè)部分梨睁,并且給出對(duì)應(yīng)的解決方案郭宝,這些解決方案有些是用面向過程的思路解決的涡贱,有些也還是停留在面向?qū)ο笾械龊5阶詈笪視?huì)給大家一個(gè)比較,然后給出結(jié)論嵌灰。

上下文規(guī)范


在進(jìn)一步地討論這些概念之前弄匕,我需要跟大家達(dá)成一個(gè)表達(dá)上的共識(shí),我會(huì)采用下面的語(yǔ)法來(lái)表達(dá)對(duì)象相關(guān)的信息:

所有的大寫字母都是類或?qū)ο蠊敛t,小寫字母表示屬性或方法?
FOO:{ isLoading, _data, render(), _switch() }   這表示一個(gè)FOO對(duì)象迁匠,isLoading、_data是它的屬性驹溃,render()城丧、_switch()是它的方法,加下劃線表示私有豌鹤。

A -> B                                          這表示從A派生出了B亡哄,A是父類。

A -> B:{ [a, b, c(), d()], e, f() }             []里面是父類的東西布疙,e蚊惯、f()是派生類的東西

B:{ [ A ], e, f() }                             省略了對(duì)父類的描述,用類名A代替灵临,其他同上

B:{ [ A ], e, f(), @c() }                       省略了對(duì)父類的描述截型,函數(shù)前加@表示重載了父類的方法。

B:{ [ A,D ], e, f() }                           多繼承儒溉,B繼承了A和D

B<protocol>                                     符合某個(gè)protocol接口的對(duì)象菠劝。

<protocol>:{foo(), bar}                         protocol這個(gè)接口中包含foo()這個(gè)方法,bar這個(gè)屬性睁搭。

foo(A, int)                                     foo這個(gè)函數(shù)赶诊,接收A類和int類型作為參數(shù)。

來(lái)园骆,我們談?wù)剬?duì)象


面向?qū)ο笏枷肴笾е豪^承舔痪、封裝、多態(tài)锌唾。這篇文章說(shuō)的是繼承锄码。當(dāng)然面向?qū)ο蠛兔嫦蜻^程都會(huì)有好有壞,但是做決定的時(shí)候晌涕,更多地還是去權(quán)衡值得不值得放棄滋捶。關(guān)于這樣的立場(chǎng)問題,我都會(huì)給出非常明確的傾向余黎,不會(huì)跟你們打太極重窟。
如果說(shuō)這個(gè)也好那個(gè)也好,那還發(fā)表毛個(gè)觀點(diǎn)惧财,那叫沒有觀點(diǎn)巡扇。

繼承

繼承從代碼復(fù)用的角度來(lái)說(shuō)扭仁,特別好用,也特別容易被濫用和被錯(cuò)用厅翔。不恰當(dāng)?shù)厥褂美^承導(dǎo)致的最大的一個(gè)缺陷特征就是高耦合乖坠。
在這里我要補(bǔ)充一點(diǎn),耦合是一個(gè)特征刀闷,雖然大部分情況是缺陷的特征熊泵,但是當(dāng)耦合成為需求的時(shí)候,耦合就不是缺陷了甸昏。耦合成為需求的例子在后面會(huì)提到戈次。
我們來(lái)看下面這個(gè)場(chǎng)景:


有一天,產(chǎn)品經(jīng)理Yuki說(shuō):

我們不光首頁(yè)要有一個(gè)搜索框筒扒,在進(jìn)去的這個(gè)頁(yè)面,也要有一個(gè)搜索框绊寻,只不過這個(gè)搜索框要多一些功能花墩,它是可以即時(shí)給用戶搜索提示的。

Casa接到這個(gè)任務(wù)澄步,他研究了一下代碼冰蘑,說(shuō):OK,沒問題~
Casa知道代碼里已經(jīng)有了一個(gè)現(xiàn)成的搜索框村缸,Casa立刻從HOME_SEARCH_BAR派生出PAGE_SEARCH_BAR
嗯祠肥,目前事情進(jìn)展到這里還不錯(cuò):

HOME_SEARCH_BAR:{textField, search(), init()}
PAGE_SEARCH_BAR:{ [ HOME_SEARCH_BAR ], overlay, prompt() }

過了幾天,產(chǎn)品經(jīng)理Yuki要求:

用戶收藏的東西太多了梯皿,我們的app需要有一個(gè)本地搜索的功能仇箱。

Casa輕松通過方法覆蓋擺平了這事兒:

HOME_SEARCH_BAR:{textField, search()}
PAGE_SEARCH_BAR:{ [ HOME_SEARCH_BAR ], overlay, prompt() }
LOCAL_SEARCH_BAR:{ [ HOME_SEARCH_BAR ], @search() }

app上線一段時(shí)間之后,UED不知哪根筋搭錯(cuò)了东羹,決定要修改搜索框的UI剂桥,UED跟Casa說(shuō):

把HOME_SEARCH_BAR的樣式改成這樣吧,里面PAGE_SEARCH_BAR還是老樣子就OK属提。

Casa表示這個(gè)看似簡(jiǎn)單的修改其實(shí)很蛋碎权逗,HOME_SEARCH_BAR的樣式一改,PAGE_SEARCH_BARLOCAL_SEARCH_BAR都會(huì)改變冤议,怎么辦呢斟薇? 與其每個(gè)手工修一遍,Casa不得已只能給HOME_SEARCH_BAR添加了一個(gè)函數(shù):initWithStyle()

HOME_SEARCH_BAR:{ textField, search(), init(), initWithStyle() }
PAGE_SEARCH_BAR:{ [ HOME_SEARCH_BAR ], overlay, prompt() }
LOCAL_SEARCH_BAR:{ [ HOME_SEARCH_BAR ], @search() }

于是代碼里面就出現(xiàn)了各種init()和initWithStyle()混用的情況恕酸。

無(wú)所謂了堪滨,先把需求應(yīng)付過去再說(shuō)。

Casa這么想蕊温。


有一天椿猎,另外一個(gè)team的leader來(lái)對(duì)Casa抱怨:

搞什么玩意兒惶岭?為毛我要把LOCAL_SEARCH_BAR獨(dú)立出來(lái)還特么連帶著把那么多文件都弄出來(lái)?我就只是想要個(gè)本地搜索的功能而已7该摺按灶!

這是因?yàn)?code>LOCAL_SEARCH_BAR依賴于它的父類HOME_SEARCH_BAR,然而HOME_SEARCH_BAR本身也帶著API相關(guān)的對(duì)象,同時(shí)還有數(shù)據(jù)解析的對(duì)象筐咧。 也就是說(shuō)鸯旁,要想把LOCAL_SEARCH_BAR移植給另外一個(gè)TEAM,拔出蘿卜帶出泥量蕊,差不多整個(gè)Networking框架都要移植過去铺罢。 嗯,Casa又要為了解耦開始一個(gè)不眠之夜了~


以上是典型的錯(cuò)誤使用繼承的案例残炮,雖然繼承是代碼復(fù)用的一種方案韭赘,但是使用繼承仍然是需要好好甄別代碼復(fù)用的方式的,不是所有場(chǎng)景的代碼復(fù)用都適用于繼承势就。

繼承是緊耦合的一種模式泉瞻,主要的體現(xiàn)就在于牽一發(fā)動(dòng)全身。

  • 第一種類型的問題是改了一處苞冯,到處都要改袖牙,但解決方案還算方便,多添加一個(gè)特定的函數(shù)(initWithStyle())就好了舅锄。只是代碼里面難看一點(diǎn)鞭达。
  • 第二種類型的問題是代碼復(fù)用的時(shí)候,要跟著把父類以及父類所有的相關(guān)依賴也復(fù)制過去皇忿,高耦合在復(fù)用的時(shí)候造成了冗余畴蹭。

對(duì)于這樣的問題,業(yè)界其實(shí)早就給出了解決方案:用組合替代繼承鳍烁。將Textfield和search模塊拆開撮胧,然后通過定義好的接口進(jìn)行交互,一般來(lái)說(shuō)可以選擇Delegate模式來(lái)交互老翘。

解決方案:

<search_protocol>:{search()}

SEARCH_LOGIC<search_protocol>

SEARCH_BAR:{textField, SEARCH_LOGIC<search_protocol>}

HOME_SEARCH_BAR:{SearchBar1, SearchLogic1}
PAGE_SEARCH_BAR:{SearchBar2, SearchLogic1}
LOCAL_SEARCH_BAR:{SearchBar2, SearchLogic2}

這樣一來(lái)芹啥,搜索框和搜索邏輯分別形成了兩個(gè)不同的組件,分別在HOME_SEARCH_BAR, PAGE_SEARCH_BAR, LOCAL_SEARCH_BAR中以不同的形態(tài)組合而成铺峭。 textFieldSEARCH_LOGIC<search_protocol>之間通過delegate的模式進(jìn)行數(shù)據(jù)交互墓怀。 這樣就解決了上面提到的兩種類型的問題。 大部分我們通過代碼復(fù)用來(lái)選擇繼承的情況卫键,其實(shí)都是變成組合比較好傀履。 因此我在團(tuán)隊(duì)中一直在推動(dòng)使用組合來(lái)代替繼承的方案。 那么什么時(shí)候繼承才有用呢?

糾結(jié)了一下钓账,貌似實(shí)在是沒什么地方非要用繼承不可的碴犬。但事實(shí)上使用繼承,我們得要分清楚層次梆暮,使用繼承其實(shí)是如何給一類對(duì)象劃分層次的問題服协。在正確的繼承方式中,父類應(yīng)當(dāng)扮演的是底層的角色啦粹,子類是上層的業(yè)務(wù)偿荷。舉兩個(gè)例子:

Object -> Model
Object -> View
Object -> Controller

ApiManager -> DetailManager
ApiManager -> ListManager
ApiManager -> CityManager

這里是有非常明確的層次關(guān)系的,我在這里也順便提一下使用繼承的3大要點(diǎn):

父類只是給子類提供服務(wù)唠椭,并不涉及子類的業(yè)務(wù)邏輯

Object并不影響Model, View, Controller的執(zhí)行邏輯和業(yè)務(wù)  
Object為子類提供基礎(chǔ)服務(wù)跳纳,例如內(nèi)存計(jì)數(shù)等

ApiManager并不影響其他的Manager  
ApiManager只是給派生的Manager提供服務(wù)而已,ApiManager做的只會(huì)是份內(nèi)的是,對(duì)于子類做的事情不參與贪嫂。

層級(jí)關(guān)系明顯寺庄,功能劃分清晰,父類和子類各做各的力崇。

Object并不參與MVC的管理中斗塘,那些都只是各自派生類自己要處理的事情

DetailManager, ListManager, CityManager都只是處理各自業(yè)務(wù)的對(duì)象  
ApiManager并不應(yīng)該涉足對(duì)應(yīng)的業(yè)務(wù)。

父類的所有變化餐曹,都需要在子類中體現(xiàn),也就是說(shuō)此時(shí)耦合已經(jīng)成為需求

Object對(duì)類的描述敌厘,對(duì)內(nèi)存引用的計(jì)數(shù)方式等台猴,都是普遍影響派生類的。  
ApiManager中對(duì)于網(wǎng)絡(luò)請(qǐng)求的發(fā)起俱两,網(wǎng)絡(luò)狀態(tài)的判斷饱狂,是所有派生類都需要的。  
此時(shí)宪彩,牽一發(fā)動(dòng)全身就已經(jīng)成為了需求休讳,是適用繼承的

此時(shí)我們回過頭來(lái)看為什么HOME_SEARCH_BAR,PAGE_SEARCH_BAR,LOCAL_SEARCH_BAR采用繼承的方案是不恰當(dāng)?shù)模?/p>

  • 他們的父類是HOME_SEARCH_BAR,父類不只提供了服務(wù)尿孔,也在一定程度上影響了子類的業(yè)務(wù)邏輯俊柔。派生出的子類也是為了要做搜索,雖然搜索的邏輯不同活合,但是互相涉及到搜索這一塊業(yè)務(wù)了雏婶。

  • 子類做搜索,父類也做搜索白指,雖然處理邏輯不同留晚,但是這是同一個(gè)業(yè)務(wù),與父類在業(yè)務(wù)上的聯(lián)系密切告嘲。在層級(jí)關(guān)系上错维,HOME_SEARCH_BAR和其派生出的LOCAL_SEARCH_BAR, PAGE_SEARCH_BAR其實(shí)是并列關(guān)系奖地,并不是上下層級(jí)關(guān)系。

  • 由于這里所謂的父類和子類其實(shí)是并列關(guān)系而不是父子關(guān)系赋焕,且并沒有需要耦合的需求参歹,相反,每個(gè)派生子類其實(shí)都不希望跟父類有耦合宏邮,此時(shí)耦合不是需求泽示,是缺陷。


總結(jié)

可見蜜氨,代碼復(fù)用也是分類別的械筛,如果當(dāng)初只是出于代碼復(fù)用的目的而不區(qū)分類別和場(chǎng)景,就采用繼承是不恰當(dāng)?shù)撵住N覀儜?yīng)當(dāng)考慮以上3點(diǎn)要素看是否符合埋哟,才能決定是否使用繼承。就目前大多數(shù)的開發(fā)任務(wù)來(lái)看郎汪,繼承出現(xiàn)的場(chǎng)景不多赤赊,主要還是代碼復(fù)用的場(chǎng)景比較多,然而通過組合去進(jìn)行代碼復(fù)用顯得要比繼承麻煩一些煞赢,因?yàn)榻M合要求你有更強(qiáng)的抽象能力抛计,繼承則比較符合直覺。然而從未來(lái)可能產(chǎn)生的需求變化和維護(hù)成本來(lái)看照筑,使用組合其實(shí)是很值得的吹截。另外,當(dāng)你發(fā)現(xiàn)你的繼承超過2層的時(shí)候凝危,你就要好好考慮是否這個(gè)繼承的方案了波俄,第三層繼承正是濫用的開端。確定有必要之后蛾默,再進(jìn)行更多層次的繼承懦铺。

所以我的態(tài)度是:萬(wàn)不得已不要用繼承,優(yōu)先考慮組合

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末支鸡,一起剝皮案震驚了整個(gè)濱河市冬念,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牧挣,老刑警劉巖刘急,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異浸踩,居然都是意外死亡叔汁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)据块,“玉大人码邻,你說(shuō)我怎么就攤上這事×砑伲” “怎么了像屋?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)边篮。 經(jīng)常有香客問我己莺,道長(zhǎng),這世上最難降的妖魔是什么戈轿? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任凌受,我火速辦了婚禮,結(jié)果婚禮上思杯,老公的妹妹穿的比我還像新娘胜蛉。我一直安慰自己,他們只是感情好色乾,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布誊册。 她就那樣靜靜地躺著,像睡著了一般暖璧。 火紅的嫁衣襯著肌膚如雪案怯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天澎办,我揣著相機(jī)與錄音嘲碱,去河邊找鬼。 笑死浮驳,一個(gè)胖子當(dāng)著我的面吹牛悍汛,可吹牛的內(nèi)容都是我干的捞魁。 我是一名探鬼主播至会,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谱俭!你這毒婦竟也來(lái)了奉件?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤昆著,失蹤者是張志新(化名)和其女友劉穎县貌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凑懂,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煤痕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摆碉。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡塘匣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巷帝,到底是詐尸還是另有隱情忌卤,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布楞泼,位于F島的核電站驰徊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏堕阔。R本人自食惡果不足惜棍厂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望印蔬。 院中可真熱鬧勋桶,春花似錦、人聲如沸侥猬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)退唠。三九已至鹃锈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞧预,已是汗流浹背屎债。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垢油,地道東北人盆驹。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像滩愁,于是被迫代替她去往敵國(guó)和親躯喇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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