引言
?小編是一名10年+的.NET Coder,期間也寫過(guò)Java、Python,從中深刻的認(rèn)識(shí)到了軟件開(kāi)發(fā)與語(yǔ)言的無(wú)關(guān)性?,F(xiàn)在小編已經(jīng)脫離了一線開(kāi)發(fā)崗位,在帶領(lǐng)團(tuán)隊(duì)的過(guò)程中,發(fā)現(xiàn)了很多的問(wèn)題,究其原因,更多的是開(kāi)發(fā)思維的問(wèn)題。所以小編通過(guò)總結(jié)自己過(guò)去十多年的軟件開(kāi)發(fā)經(jīng)驗(yàn),為年輕一輩的軟件開(kāi)發(fā)者從思維角度提供一些建議,希望能對(duì)大家有所幫助。
在面向?qū)ο缶幊蹋∣OP)中,繼承(Inheritance)是另一個(gè)核心概念,它不僅是實(shí)現(xiàn)代碼復(fù)用的工具,更是一種強(qiáng)大的設(shè)計(jì)思維。繼承允許子類從父類獲取或覆蓋屬性和方法,同時(shí)支持多態(tài)性、抽象類、接口等高級(jí)特性。這是眾所周知的定義。
一. 從生活出發(fā)理解繼承
我們?cè)谏钪凶钕冉佑|的是細(xì)節(jié),比如看到各種動(dòng)作后,才開(kāi)始對(duì)它們進(jìn)行分類,才會(huì)去思考他們的叫聲是不同,走路也是不同的。這種從細(xì)節(jié)到整體的思維方式,恰恰可以指導(dǎo)我們?cè)诰幊讨泻侠淼厥褂美^承。
自下而上,從細(xì)節(jié)出發(fā),抽象出共性
比如看到狗、貓、鳥(niǎo),然后觀察它們的行為,隨后,我們總結(jié)它們有一些共同點(diǎn),比如都會(huì)吃
和睡覺(jué)
,于是抽象出“動(dòng)物”這個(gè)概念,也知道了動(dòng)物都需要吃和睡。在編程中,這種思維方式同樣適用:
- 步驟:先觀察具體的對(duì)象(比如
Dog
、Cat
),列出它們的屬性和行為,然后找出共性(如Eat()
和Sleep()
)。 - 應(yīng)用:將這些共性提取到一個(gè)抽象的父類(比如
Animal
)中,而具體的特性(比如狗會(huì)舔人 - Lick()
、貓會(huì)抓人 - ArrestAb()
、鳥(niǎo)會(huì)飛 - Fly()
)則留在子類中。 - 思考問(wèn)題:?jiǎn)栕约?,“這些對(duì)象有哪些共同的屬性和行為?” 這些共性將成為繼承的基礎(chǔ)。
例如:
| Animal (父類) |
| - Eat() |
| - Sleep() |
|
|
|
|
| Dog (子類) Cat (子類) Bird() |
|
|
|
|
|
|
| Lick() - ArrestAb() - Fly() |
|
|
自上而下,逐步分解,逐步求精
雖然我們從細(xì)節(jié)開(kāi)始,但設(shè)計(jì)繼承時(shí),可以反過(guò)來(lái)從抽象的父類入手,再逐步細(xì)化到子類。這就像在動(dòng)物分類學(xué)中,我們已經(jīng)具備了動(dòng)物界的相關(guān)知識(shí),所以會(huì)先定義“動(dòng)物”的大框架,然后再細(xì)分出哺乳動(dòng)物、鳥(niǎo)類等:
- 步驟:先定義一個(gè)通用的父類(
Animal
),包含所有子類共享的屬性和方法,然后在子類中添加特定功能。 - 好處:這種方法讓代碼結(jié)構(gòu)更清晰,易于擴(kuò)展。
- 思考問(wèn)題:先問(wèn)“這個(gè)系統(tǒng)整體需要什么通用邏輯?” 再考慮“每個(gè)具體對(duì)象需要什么特殊功能?”
判斷“is-a”關(guān)系
生活中,狗是動(dòng)物,貓是動(dòng)物,但狗不是貓。這種“is-a”關(guān)系是繼承的核心依據(jù):
- 原則:只有當(dāng)子類與父類存在嚴(yán)格的“is-a”關(guān)系時(shí),才使用繼承。例如,
Dog
是Animal
,但Dog
不是Vehicle
。 - 思考問(wèn)題:在設(shè)計(jì)時(shí),問(wèn)自己,“這個(gè)子類真的是父類的一種嗎?” 如果答案是否定的,就不要強(qiáng)行使用繼承。
關(guān)注擴(kuò)展性
生活中,動(dòng)物分類可以不斷擴(kuò)展,比如發(fā)現(xiàn)新物種時(shí),可以將其歸入現(xiàn)有類別或創(chuàng)建新類別。編程中也一樣:
- 建議:設(shè)計(jì)父類時(shí),考慮未來(lái)的擴(kuò)展性。比如,可以在
Animal
中定義抽象方法(如MakeSound()
),讓子類去實(shí)現(xiàn)具體的叫聲。 - 思考問(wèn)題:“如果以后需要添加新的子類,這個(gè)父類設(shè)計(jì)是否足夠靈活?”
例如:
| Animal |
| - Eat() |
| - Sleep() |
| - MakeSound() [抽象方法] |
|
|
|
|
| Dog Cat |
|
|
|
|
|
|
| MakeSound() - MakeSound() |
|
|
| 輸出 "汪汪" 輸出 "喵喵" |
|
|
繼承的掛葡萄式比喻
經(jīng)典的繼承示意圖在面向?qū)ο笤O(shè)計(jì)中,父類通常定義了一些通用的屬性和方法,作為所有子類的共享基礎(chǔ)。子類通過(guò)繼承這個(gè)父類,可以直接使用這些共享特性,同時(shí)根據(jù)自己的需求進(jìn)行特性化。
繼承可以被看作是一種占位機(jī)制,通過(guò)父類定義一個(gè)通用的框架或接口,然后由子類根據(jù)具體需求來(lái)實(shí)現(xiàn)或擴(kuò)展任務(wù)。
反思
在生活中,如果我們把動(dòng)物分類得過(guò)于細(xì)致,比如分成“會(huì)飛的動(dòng)物”“會(huì)游泳的動(dòng)物”,可能會(huì)導(dǎo)致混亂。如同上圖的分叉線繼續(xù)分下去,會(huì)很難把控,整個(gè)結(jié)構(gòu)也會(huì)線的混亂,編程中也是如此:
- 問(wèn)題:過(guò)深的繼承層次(如
Animal -> Mammal -> Canine -> Dog
)會(huì)讓代碼難以維護(hù)。 - 建議:保持繼承層次簡(jiǎn)單,通常不超過(guò)三層。
- 問(wèn)題思考:“這個(gè)繼承層次是否必要?能不能用其他方式替代?”
靈活結(jié)合組合
有時(shí)候,細(xì)節(jié)特性不適合用繼承表達(dá)。比如,“會(huì)飛”與其說(shuō)是鳥(niǎo)的類型,不如說(shuō)是鳥(niǎo)的一種能力:
- 替代方案:使用組合(has-a)關(guān)系,而不是繼承。例如,給
Bird
添加一個(gè)FlyBehavior
對(duì)象,而不是讓Bird
繼承一個(gè)FlyableAnimal
類。 - 問(wèn)題思考:“這個(gè)特性是對(duì)象的一種類型,還是對(duì)象的一部分?” 如果是部分,組合可能更合適。
例如:
| Bird |
| - FlyBehavior (組合的對(duì)象) |
| - Fly() 方法 |
二、面向?qū)ο笙碌睦^承
定義
通過(guò)is-a
關(guān)系實(shí)現(xiàn)層次化的代碼復(fù)用和類型兼容,結(jié)合行為的動(dòng)態(tài)適配和資源管理的層次依賴,在封裝約束下構(gòu)建模塊化、可擴(kuò)展的系統(tǒng)。
規(guī)則
?繼承的最一般規(guī)則是:層次化復(fù)用與行為適配。
繼承的核心在于通過(guò)層次化的代碼復(fù)用和行為的動(dòng)態(tài)適配,構(gòu)建模塊化、可擴(kuò)展的系統(tǒng)。其一般規(guī)律可以歸納為以下幾個(gè)普適原則,無(wú)論具體語(yǔ)言或?qū)崿F(xiàn)細(xì)節(jié)如何變化,這些規(guī)律始終成立:
is-a
關(guān)系的層次復(fù)用
- 本質(zhì):繼承通過(guò)
is-a
關(guān)系(子類是父類的一種),允許子類在復(fù)用父類定義(屬性和方法)的基礎(chǔ)上,擴(kuò)展或特化其行為。 - 規(guī)則:子類繼承父類的所有可訪問(wèn)成員,形成一個(gè)從通用到具體的層次結(jié)構(gòu)。每一層繼承都在前一層的基礎(chǔ)上增加特異性,從而實(shí)現(xiàn)代碼的逐步精煉和重用。
- 意義:這種層次化設(shè)計(jì)避免了重復(fù)定義通用功能,同時(shí)支持功能的逐步細(xì)化。
類型兼容性支持多態(tài)
- 本質(zhì):子類對(duì)象可以被視為父類對(duì)象,允許在需要父類的地方使用子類實(shí)例。
- 規(guī)則:繼承建立了類型間的兼容性(子類型關(guān)系),使得系統(tǒng)可以在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類型動(dòng)態(tài)選擇行為(多態(tài)性)。
- 意義:類型兼容性是多態(tài)的基礎(chǔ),確保了接口的統(tǒng)一性和實(shí)現(xiàn)的多樣性,增強(qiáng)了系統(tǒng)的靈活性。
行為覆蓋與動(dòng)態(tài)適配
- 本質(zhì):子類可以通過(guò)重寫(override)父類方法,覆蓋或調(diào)整父類的行為。
- 規(guī)則:繼承允許子類在復(fù)用父類代碼的同時(shí),動(dòng)態(tài)適配行為以滿足特定需求。運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類型決定執(zhí)行哪個(gè)方法實(shí)現(xiàn)。
- 意義:這種動(dòng)態(tài)適配機(jī)制使得同一接口可以有多種實(shí)現(xiàn),支持系統(tǒng)的可擴(kuò)展性和個(gè)性化需求。
資源管理的層次依賴
- 本質(zhì):子類的初始化和銷毀依賴于父類的初始化和銷毀。
- 規(guī)則:對(duì)象的構(gòu)造從父類到子類逐層進(jìn)行,析構(gòu)則反向進(jìn)行,確保資源分配和釋放的邏輯一致性。
- 意義:這種順序規(guī)律保證了繼承鏈中每一層的資源管理不會(huì)出現(xiàn)未定義行為,維護(hù)了系統(tǒng)的穩(wěn)定性。
訪問(wèn)控制的邊界約束
- 本質(zhì):繼承中父類的成員可見(jiàn)性通過(guò)訪問(wèn)控制(public、protected、private)定義,子類只能訪問(wèn)授權(quán)的部分。
- 規(guī)則:子類對(duì)父類成員的訪問(wèn)受限于封裝邊界,private成員對(duì)子類不可見(jiàn),protected和public成員可被復(fù)用或調(diào)整。
- 意義:訪問(wèn)控制在復(fù)用代碼的同時(shí)保護(hù)了父類的實(shí)現(xiàn)細(xì)節(jié),維持了封裝性與繼承性的平衡。
?這些規(guī)則不僅是繼承的表層特征,還反映了其在類型系統(tǒng)、內(nèi)存管理和運(yùn)行時(shí)行為中的深層作用:
- 類型系統(tǒng):繼承通過(guò)子類型關(guān)系支持類型安全和多態(tài),確保子類可以替代父類(里氏替換原則)。
- 內(nèi)存管理:子類對(duì)象包含父類對(duì)象的內(nèi)存布局,保證了類型兼容性和直接訪問(wèn)的可能。
- 運(yùn)行時(shí)行為:動(dòng)態(tài)方法綁定以支持行為的運(yùn)行時(shí)適配。
三、繼承的深層意義:層次化分解復(fù)雜問(wèn)題
1. 從抽象到具體的設(shè)計(jì)過(guò)程
?這里的繼承用到了一種自上而下的設(shè)計(jì)方法,開(kāi)發(fā)者可以先從抽象的層面定義系統(tǒng)的整體結(jié)構(gòu)和行為,然后逐步細(xì)化到具體的實(shí)現(xiàn)細(xì)節(jié),這也是一個(gè)樹(shù)形可追蹤的過(guò)程的。
- 抽象層面:通過(guò)定義父類或抽象類,開(kāi)發(fā)者可以先關(guān)注系統(tǒng)的“大圖景”。例如,一個(gè)抽象的
Shape
類可以定義所有圖形共有的方法,如Draw()
和Resize()
,而無(wú)需立即考慮具體圖形的繪制方式。 - 具體實(shí)現(xiàn):子類通過(guò)繼承父類并實(shí)現(xiàn)具體方法,將抽象的概念轉(zhuǎn)化為可操作的代碼。例如,
Circle
和Rectangle
類可以分別實(shí)現(xiàn)自己的Draw()
方法,完成具體的繪制邏輯。
這種從上到下的分解方式,使開(kāi)發(fā)者能夠先勾勒出系統(tǒng)的整體框架,再逐步填充細(xì)節(jié),確保設(shè)計(jì)的一致性和連貫性。
2. 層次化分解復(fù)雜問(wèn)題
繼承允許將復(fù)雜的問(wèn)題分解為多個(gè)層次。父類負(fù)責(zé)定義通用的屬性和行為,子類則根據(jù)具體需求擴(kuò)展或修改這些內(nèi)容。這種層次化的結(jié)構(gòu)使開(kāi)發(fā)者可以專注于某個(gè)層次的功能,而不必同時(shí)應(yīng)對(duì)整個(gè)系統(tǒng)的復(fù)雜性。
例如,在一個(gè)圖形編輯器中:
- 中層:
TwoDShape
和ThreeDShape
類繼承Shape
,分別處理二維和三維圖形的共性。 - 底層:
Circle
、Rectangle
等類繼承TwoDShape
,實(shí)現(xiàn)具體的二維圖形功能。
這種層次化的設(shè)計(jì)讓系統(tǒng)的復(fù)雜性被逐步分解,每個(gè)層次都更加易于理解和維護(hù)。
3. 提供擴(kuò)展點(diǎn)而不破壞整體結(jié)構(gòu)
繼承通過(guò)“鉤子”(如虛方法或抽象方法)提供擴(kuò)展點(diǎn),允許子類在不修改父類代碼的情況下添加具體實(shí)現(xiàn)。這種機(jī)制在設(shè)計(jì)中非常有用,因?yàn)樗屛覀兛梢栽诒3终w框架穩(wěn)定的同時(shí),逐步加入細(xì)節(jié)。
例如,在一個(gè)支付系統(tǒng)中:
- 父類:
PaymentProcessor
定義了支付的通用流程,如驗(yàn)證、扣款、記錄日志等。 - 子類:
CreditCardPayment
和PayPalPayment
通過(guò)重寫具體步驟,實(shí)現(xiàn)不同支付方式的細(xì)節(jié)。
這種設(shè)計(jì)遵循“開(kāi)閉原則”(對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉),確保系統(tǒng)的穩(wěn)定性與靈活性并存。
四、繼承在架構(gòu)設(shè)計(jì)中的應(yīng)用
1. 模塊化和層次化
在架構(gòu)設(shè)計(jì)中,繼承常被用來(lái)構(gòu)建模塊化和層次化的系統(tǒng)結(jié)構(gòu)。父類定義通用的行為和接口,子類則根據(jù)具體模塊的需求實(shí)現(xiàn)細(xì)節(jié)。這種設(shè)計(jì)不僅使系統(tǒng)更具條理性,還能將問(wèn)題拆分為更易于管理的部分。
在架構(gòu)設(shè)計(jì)中,繼承的真正力量在于它提供了一種自下而上的設(shè)計(jì)方法,引導(dǎo)我們從局部到整體逐步抽象問(wèn)題。開(kāi)發(fā)者可以先從抽象的層面定義系統(tǒng)的整體結(jié)構(gòu)和行為,然后逐步細(xì)化到具體的實(shí)現(xiàn)細(xì)節(jié),這也是一個(gè)樹(shù)形可追蹤的過(guò)程的。
例如,在一個(gè)企業(yè)級(jí)應(yīng)用中:
- 基礎(chǔ)層:
BaseController
類定義了所有控制器的通用邏輯,如身份驗(yàn)證、日志記錄等。 - 業(yè)務(wù)層:
UserController
和OrderController
繼承BaseController
,并實(shí)現(xiàn)各自的業(yè)務(wù)邏輯。
這種層次化的設(shè)計(jì)使開(kāi)發(fā)者可以專注于業(yè)務(wù)邏輯,而不必重復(fù)處理基礎(chǔ)功能。
2. 支持設(shè)計(jì)模式
繼承在許多設(shè)計(jì)模式中扮演關(guān)鍵角色,幫助系統(tǒng)實(shí)現(xiàn)靈活性和可擴(kuò)展性。
- 模板方法模式:父類定義一個(gè)方法的框架,子類通過(guò)繼承實(shí)現(xiàn)具體步驟。例如,一個(gè)
Beverage
類定義了制作飲料的通用流程,Coffee
和Tea
類通過(guò)繼承實(shí)現(xiàn)具體的沖泡步驟。 - 策略模式:通過(guò)繼承不同的策略類,系統(tǒng)可以在運(yùn)行時(shí)選擇不同的行為。
- 裝飾器模式:雖然通常與組合相關(guān),但在某些情況下,繼承也可以實(shí)現(xiàn)裝飾器效果,擴(kuò)展對(duì)象的功能。
3. 框架和庫(kù)的擴(kuò)展
在框架或庫(kù)的設(shè)計(jì)中,繼承常被用來(lái)提供可擴(kuò)展的鉤子(hooks)。開(kāi)發(fā)者可以通過(guò)繼承基類并重寫方法,定制框架的行為,使其適應(yīng)特定場(chǎng)景。
五、繼承與思維模式的轉(zhuǎn)變
1. 分清整體和局部的思維
繼承鼓勵(lì)開(kāi)發(fā)者從整體到局部逐步分解問(wèn)題:
- 先定義框架:通過(guò)父類或抽象類定義系統(tǒng)的整體結(jié)構(gòu)和行為。
- 再細(xì)化細(xì)節(jié):子類負(fù)責(zé)實(shí)現(xiàn)具體的功能,逐步完善系統(tǒng)。
?當(dāng)你在做軟件開(kāi)發(fā)的時(shí)候,需要首先明白你想要解決什么問(wèn)題,而這個(gè)問(wèn)題本身就是整體。設(shè)計(jì)父類的時(shí)候,需要想到你只是在整體上對(duì)該對(duì)象或者場(chǎng)景進(jìn)行描述。而當(dāng)我們進(jìn)行繼承操作的時(shí)候,更多的應(yīng)該要想到,我們是在基于父類做一些細(xì)化,但不可以越界發(fā)揮。
這種思維方式避免了在設(shè)計(jì)初期陷入瑣碎細(xì)節(jié)的困境,提升了設(shè)計(jì)的效率和質(zhì)量。
2. 關(guān)注點(diǎn)分離
通過(guò)將通用描述與行為(父類)和具體描述與行為(子類)分開(kāi),繼承讓我們能夠?qū)W⒂诋?dāng)前的設(shè)計(jì)層次,而不必同時(shí)處理整個(gè)系統(tǒng)的復(fù)雜性。這種關(guān)注點(diǎn)分離的思維,幫助開(kāi)發(fā)者更高效地管理復(fù)雜性。
3. 平衡抽象與細(xì)節(jié)
繼承在抽象的穩(wěn)定性與細(xì)節(jié)的靈活性之間找到了平衡:
- 抽象的穩(wěn)定性:父類定義了系統(tǒng)的核心部分,通常不易改變。
- 細(xì)節(jié)的靈活性:子類負(fù)責(zé)實(shí)現(xiàn)具體功能,可以根據(jù)需求靈活調(diào)整。
?面對(duì)問(wèn)題的時(shí)候,首先應(yīng)該直面你面對(duì)的是什么問(wèn)題,只要明確了問(wèn)題,然后進(jìn)行一般性的定性后,抽象也就出來(lái)了。而當(dāng)你在進(jìn)行繼承操作的時(shí)候,更多的應(yīng)該要想到,我們需要基于父類做一些細(xì)化和補(bǔ)充,但不可以越界發(fā)揮。
這種平衡使得系統(tǒng)既能保持穩(wěn)定,又能適應(yīng)變化,為軟件的可擴(kuò)展性和可維護(hù)性奠定了基礎(chǔ)。
4. 平衡穩(wěn)定與變化
- 代碼復(fù)用不一定是繼承:在某些情況下,使用委托或輔助類可能比繼承更合適。
- 接口 vs 繼承:當(dāng)只需要行為規(guī)范而不需要實(shí)現(xiàn)時(shí),接口可能比繼承更合適。
?始終謹(jǐn)記,通用的往往是穩(wěn)定的,所以需要抽象出來(lái);具體的才是頻繁變化的,所以需要把變化的部分劃分出來(lái),使之可以在繼承框架下既能重用也能獨(dú)立變化,而不引發(fā)較大的影響,這就是繼承的真正價(jià)值 —— 它幫助開(kāi)發(fā)者在抽象與細(xì)節(jié)之間找到平衡,通過(guò)自下而上和自下而上的設(shè)計(jì)方法,引導(dǎo)我們從在局部與整體之間逐步完善對(duì)問(wèn)題的認(rèn)識(shí)。
結(jié)語(yǔ)
繼承是面向?qū)ο缶幊痰暮诵臋C(jī)制,不僅提供了代碼復(fù)用的便利,更體現(xiàn)了一種深刻的思維方式。通過(guò)繼承,開(kāi)發(fā)者能夠在抽象與細(xì)節(jié)之間找到平衡,配合自上而下和自下而上的設(shè)計(jì)方法,逐步分解問(wèn)題,從而提升系統(tǒng)的健壯性和可維護(hù)性。
在軟件開(kāi)發(fā)的多個(gè)領(lǐng)域,例如架構(gòu)設(shè)計(jì)、設(shè)計(jì)模式以及生命周期管理等,繼承都扮演著不可或缺的角色。它為構(gòu)建靈活、可擴(kuò)展的系統(tǒng)提供了強(qiáng)有力的支持。
然而,繼承并非萬(wàn)能的解決方案。如果過(guò)度或不當(dāng)使用繼承,可能會(huì)導(dǎo)致類層次結(jié)構(gòu)變得復(fù)雜,增加系統(tǒng)的耦合度,進(jìn)而提高維護(hù)成本。
因此,在使用繼承時(shí),開(kāi)發(fā)者需要謹(jǐn)慎設(shè)計(jì),確保類層次結(jié)構(gòu)清晰、類與類之間的關(guān)系合理。同時(shí),在適當(dāng)?shù)膱?chǎng)景下,應(yīng)結(jié)合組合、接口等其他設(shè)計(jì)原則,以構(gòu)建高質(zhì)量的軟件系統(tǒng)。做到這些,更多的依靠經(jīng)驗(yàn)的積累與思維的提升。
通過(guò)正確使用繼承,我們不僅能提升代碼的邏輯性、可讀性和可維護(hù)性,還能培養(yǎng)一種從具體到抽象、再回到具體的思維方式。希望大家從思維角度理解繼承,用好繼承。