高級程序員必須要會的五種編程范式
今天咱們來聊聊一個聽起來挺高大上的話題——編程范式。這詞兒聽起來可能有點唬人,但其實它就是描述編程時組織代碼的不同風格和方法。
我會盡量用簡單的話給大家解釋清楚,每種范式到底是怎么一回事。這樣,當別人說起“面向對象”、“函數式”或者“聲明式”這些詞兒時,你就能心領神會了。
這篇文章主要是個簡單的理論介紹,當然啦,咱們也會看一些偽代碼和實際的代碼示例。
咱們開始吧!
什么是編程范式?
所謂的編程范式,其實就是我們組織程序或者編程語言的不同方式和風格。每種范式都有自己的一套結構、特性,以及解決常見編程問題的方法和觀點。
為啥會有這么多不同的編程范式呢?這問題其實和為啥有這么多編程語言差不多。不同的范式適合解決不同類型的問題,所以針對不同項目使用不同的范式是有意義的。
隨著時間的推移,軟件和硬件的進步也推動了不同方法的發展。再加上我們人類的創造力,我們總喜歡創造新東西,改進前人的成果,把工具調整成我們喜歡的樣子,或者看起來更高效的方式。
所以,今天我們在編寫和組織程序時,有很多選擇。
編程范式不是什么
要明確一點,編程范式不是編程語言,也不是工具。你不能用范式來“構建”任何東西。它們更像是一套理念和指導原則,是很多人達成共識、遵循并不斷發展的東西。
編程語言并不總是和某個特定的范式綁定在一起。有些語言在設計時就考慮了特定的范式,并且提供了更多促進該范式編程的特性(比如Haskell和函數式編程)。
但也有很多“多范式”的語言,意味著你可以根據自己的需要,讓代碼適應不同的范式(比如JavaScript和Python)。
同時,不同的編程范式并不是互斥的,你完全可以在同一個項目中同時使用來自不同范式的實踐。
我為啥要關心這個?
簡單來說,就是為了增加你的知識面。
詳細點說,我覺得了解編程的多種方法很有趣。探索這些話題可以幫助你開闊思維,跳出你已經熟悉的工具和框架。
而且,這些術語在編程界經常被提及,所以有一個基本的了解,將有助于你更好地理解其他相關的主題。
好了,既然我們已經介紹了編程范式是什么和不是什么,接下來就讓我們一起來看看最流行的幾種范式,了解它們的主要特點,并進行比較。
要記住,這個列表并不全面。還有一些其他的編程范式沒有在這里涵蓋到,但我會介紹最流行和最廣泛使用的幾種。
命令式編程
命令式編程由一系列詳細的指令組成,這些指令按給定的順序提供給計算機執行。它之所以被稱為“命令式”,是因為作為程序員,我們會非常具體地告訴計算機必須做什么。
命令式編程關注的是描述程序是如何一步步運作的。
假如你想烤一個蛋糕,你的命令式程序可能看起來像這樣(我可不是個厲害的廚師,所以別太苛刻哦 ??):
1. 在一個碗里倒入面粉
2. 往同一個碗里打入幾個雞蛋
3. 往同一個碗里倒些牛奶
4. 混合這些食材
5. 將混合物倒入模具
6. 烤上35分鐘
7. 讓它冷卻下來
用實際的代碼示例來說,假設我們要過濾一個數字數組,只保留大于5的元素。我們的命令式代碼可能長這樣:
const nums = [1, 4, 3, 6, 7, 8, 9, 2];
const result = [];
for (let i = 0; i < nums.length; i++) {
if (nums[i] > 5) result.push(nums[i]);
}
console.log(result); // 輸出:[6, 7, 8, 9]
我們告訴程序遍歷數組中的每個元素,將元素的值與5進行比較,如果元素大于5,就把它加入到新數組中。
我們的指令非常詳細具體,這就是命令式編程的核心。
過程式編程
過程式編程是命令式編程的一個延伸,它增加了函數(也稱為“過程”或“子程序”)的特性。
在過程式編程中,鼓勵用戶將程序執行細分為函數,作為提高模塊化和組織性的一種方式。
繼續我們的蛋糕例子,過程式編程可能是這樣的:
function pourIngredients() {
// 在一個碗里倒入面粉
// 往同一個碗里打入幾個雞蛋
// 往同一個碗里倒些牛奶
}
function mixAndTransferToMold() {
// 混合食材
// 將混合物倒入模具
}
function cookAndLetChill() {
// 烤上35分鐘
// 讓它冷卻下來
}
pourIngredients();
mixAndTransferToMold();
cookAndLetChill();
你可以看到,通過實現函數,我們可以直接看文件末尾的三個函數調用,對我們的程序做什么就有了一個清晰的了解。
這種簡化和抽象是過程式編程的好處之一。但在函數內部,我們仍然使用的是命令式代碼。
函數式編程
函數式編程將函數的概念提升到了一個新的層次。
在函數式編程中,函數被視為一級公民,這意味著它們可以被賦值給變量,作為參數傳遞,也可以作為其他函數的返回值。
另一個關鍵概念是純函數。一個純函數只依賴于它的輸入來生成結果。給定相同的輸入,它總是產生相同的結果。此外,它不會產生任何副作用(即不會對函數外部的環境產生任何改變)。
有了這些概念,函數式編程鼓勵我們用函數來編寫大部分程序(驚訝吧??)。它還主張代碼的模塊化和無副作用,這使得在代碼庫中更容易識別和分離責任,從而提高了代碼的可維護性。
回到數組過濾的例子,我們可以看到,在命令式范式中,我們可能會使用一個外部變量來存儲函數的結果,這可以被視為一個副作用。
const nums = [1, 4, 3, 6, 7, 8, 9, 2];
const result = []; // 外部變量
for (let i = 0; i < nums.length; i++) {
if (nums[i] > 5) result.push(nums[i]);
}
console.log(result); // 輸出:[6, 7, 8, 9]
要將其轉換為函數式編程,我們可以這樣做:
const nums = [1, 4, 3, 6, 7, 8, 9, 2];
function filterNums() {
const result = []; // 內部變量
for (let i = 0; i < nums.length; i++) {
if (nums[i] > 5) result.push(nums[i]);
}
return result;
}
console.log(filterNums()); // 輸出:[6, 7, 8, 9]
代碼幾乎一樣,但我們把迭代包裝在了一個函數里,并且在函數內部也存儲了結果數組。這樣,我們可以確保函數不會修改它作用域之外的任何東西。它只創建了一個變量來處理它自己的信息,一旦執行完成,那個變量也就不存在了。
聲明式編程
聲明式編程的核心是隱藏復雜性,讓編程語言更接近人類的語言和思維方式。它與命令式編程正好相反,因為程序員不需要給出關于計算機應該如何執行任務的指令,而是關于需要什么結果。
舉個例子,用數組過濾的故事來說,聲明式的方法可能是這樣的:
const nums = [1, 4, 3, 6, 7, 8, 9, 2];
console.log(nums.filter(num => num > 5)); // 輸出:[6, 7, 8, 9]
看到沒,用filter函數時,我們并沒有明確告訴計算機要遍歷數組或者把值存儲到另一個數組里。我們只是說出了我們想要什么("filter")以及滿足的條件("num > 5")。
這樣的好處是,代碼更容易閱讀和理解,通常也更簡短。JavaScript中的filter、map、reduce和sort函數就是聲明式代碼的很好例子。
另一個好例子是現代的JS框架/庫,比如React。看看這段代碼:
<button onClick={() => console.log('你點擊了我!')}>點擊我</button>
這里我們有一個按鈕元素,帶有一個事件監聽器,當按鈕被點擊時,會觸發console.log函數。
React使用的JSX語法將HTML和JS混合在一起,這讓編寫應用程序變得更加簡單快捷。但實際上,瀏覽器并不會直接讀取和執行這樣的代碼。React代碼最終會被轉譯成常規的HTML和JS,這才是瀏覽器真正運行的東西。
JSX是聲明式的,因為它的目的是為開發者提供一個更友好、更高效的工作接口。
關于聲明式編程,一個重要的事情是,計算機在背后實際上是將這些信息作為命令式代碼來處理的。
以數組為例,計算機仍然會像在for循環中那樣遍歷數組,但作為程序員,我們不需要直接編寫這些代碼。聲明式編程所做的,就是將那些復雜性從程序員的直接視野中隱藏起來。
面向對象編程
面向對象編程(OOP)是最流行的編程范式之一。
OOP的核心概念是將關注點分離到被編碼為對象的實體中。每個實體都會組合一組特定的信息(屬性)和可以由實體執行的操作(方法)。
OOP大量使用類(這是從程序員設置的藍圖或樣板開始創建新對象的一種方式)。從類創建的對象稱為實例。
繼續我們的偽代碼烹飪示例,假設在我們的面包店中,我們有一個主廚(叫Frank)和一個助理廚師(叫Anthony),他們每個人在烘焙過程中都有特定的責任。如果我們使用OOP,我們的程序可能看起來像這樣:
// 創建對應每個實體的兩個類
class Cook {
constructor(name) {
this.name = name;
}
mixAndBake() {
// 混合食材
// 將混合物倒入模具
// 烤35分鐘
}
}
class AssistantCook {
constructor(name) {
this.name = name;
}
pourIngredients() {
// 在一個碗里倒入面粉
// 在同一個碗里打入幾個雞蛋
// 在同一個碗里倒些牛奶
}
chillTheCake() {
// 讓其冷卻下來
}
}
// 從每個類實例化一個對象
const Frank = new Cook('Frank');
const Anthony = new AssistantCook('Anthony');
// 調用每個實例對應的方法
Anthony.pourIngredients();
Frank.mixAndBake();
Anthony.chillTheCake();
OOP的好處是,它通過明確的責任和關注點分離,促進了對程序的理解。
總結
正如我們所看到的,編程范式是我們面對編程問題的不同方式,以及組織我們代碼的方式。
命令式、過程式、函數式、聲明式和面向對象范式是今天最受歡迎和廣泛使用的范式之一。了解它們的基礎知識對于一般知識和更好地理解編碼世界的其他主題都有好處。