編程範式(Programming paradigm)指計算機編程的基本風格或典型模式。html
編程範型提供了(同時決定了)程序員對程序執行的見解。例如,在面向對象編程中,程序員認爲程序是一系列相互做用的對象,而在函數式編程中一個程序會被看做是一個無狀態的函數計算的序列。前端
着眼於解決問題的不一樣方式,編程範式現存許多種,其中如:面向過程、面向對象、函數式編程等範式,咱們對此比較熟悉,他們也常常出如今咱們的視野中。爲了進一步加深對編程範式的認識,這裏介紹幾種經常使用的編程範式。程序員
面向過程編程,也被稱之爲命令式編程,是一種最原始,也是咱們最熟悉,平常工做中使用較多的一種編程範式。從本質上講,它是「馮.諾伊曼機」運行機制的抽象,它的編程思惟方式源於計算機指令的順序排列。算法
面向過程的核心是將解決問題的步驟分析出來,用函數將這些步驟實現,而後再被依次調用。編程
好比一個經典的例子:後端
把大象放在冰箱分爲幾步?微信
第一步:把冰箱打開ide
第二步:把大象裝進去函數式編程
第三步:把冰箱門關上函數
得益於面向過程的直觀性,以及最接近於程序的實際運行,到如今,它仍然被大範圍的使用。
可是它不適合某類問題的解決,例如那些非結構化的具備複雜算法的問題。它必須對一個算法加以詳盡的說明,而且其中還要包括執行這些指令或語句的順序。實際上,給那些非結構化的具備複雜算法的問題給出詳盡的算法是極其困難的;而且它強調順序相當重要,而這並不適合全部問題。
面向對象編程(OOP:Object Oriented Programming)把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數,它最先由 Alan Kay 在 1966 年或 1967 年在研究生期間提出。
與面向過程編程不一樣,在面向過程編程中,數據和處理數據的函數彼此獨立,咱們須要先將數據處理成函數能接受的格式,而後調用相關函數。而在在面向對象中,數據和處理數據的函數都在一個類中,經過初始化實例傳遞數據。
現現在,當談及面向對象時,下意識的就會聯想出它的三個特性:封裝、繼承與多態。
class Animal {
constructor( public name: string ) {}
eat() {
console.log('eat food')
}
}
class Cat extends Animal {
constructor() {
super('Cat')
}
}
class Bird extends Animal {
constructor() {
super('Dog')
}
}
複製代碼
網上也有一張的圖,形象的解釋了面向對象中的一些常見概念:
用面向對象的方式來解釋「把大象關進冰箱」:
冰箱.開門()
冰箱.放入(大象)
冰箱.關門()
面向切面編程(AOP:Aspect Oriented Program)是對面向對象編程的一個補充。咱們知道在面向對象編程時,沒法作到重複使用與主業務無關的某些方法。好比 A、B 兩個類,若是都要使用日誌功能,按照面向對象的設計原則,你必須在每一個類裏面都寫上這樣一個方法,儘管他們幾乎一摸同樣。固然你也能夠選擇將這樣一段代碼放到一個獨立的類裏,而後再在A、B類中調用,可是這樣就等於造成了一個強耦合的關係,它的改變會影響到這兩個類。面向切面編程,正是解決的這樣一個問題:把和主業務無關的事情,放到代碼外面去作。
好比在前端,實現一個代碼埋單時:
若是不使用 AOP 你的代碼多是這樣:
class Test {
add(paras: Params) {
// 埋點代碼
// 其餘代碼
}
}
複製代碼
可是這樣會形成耦合,且不能複用。
使用 AOP 時:
function log(): MethodDecorator {
return (target, key, descriptor) => {
// 在這裏你能夠加上你的埋點接口
// 或者作一些其餘事情
return descriptor;
};
}
class Test {
@log()
add(paras: Params) {
//
}
}
複製代碼
再好比後端接口的鑑權,你可能會在中間件中處理:
function guardsMiddle(req, res, next) {
// 這裏可能會有各類判斷
// 判斷 url 是不是指定 url,而後再判斷是否有權限
next()
}
複製代碼
或者你會在某個具體的 controller/services 中處理:
class SomeController {
getUserInfo() {
// 現判斷是否有對應的權限
// 再作具體的處理
}
}
複製代碼
在 AOP 中,則多是這樣:
@Controller()
class SomeController {
@Get('userInfo')
@Roles('admin')
getUserInfo() {
//
}
}
複製代碼
同面向切面編程同樣,面向接口也是對面向對象的一種補充。它規定了實現本接口的類必須擁有的一組規則。好比:
interface Animal {
eat(): void;
}
class Cat implements Animal {
eat() {}
}
class Dog implements Animal {
eat() {}
}
複製代碼
在開發 vscode 插件時,會使用不少從 vscode 中暴露出來的接口:
class StatusBar implements vscode.Disposable {
dispose() {
// xxx
}
}
class TargetTreeProvider implements vscode.TreeDataProvider<{}> {
getChildren() {
// xxx
}
}
複製代碼
你可能會有疑問,我也可使用抽象類實現,那麼區別是什麼?
abstract class Animal {
abstract eat(): void;
}
class Cat extends Animal {
eat() {}
}
複製代碼
在文中這兩個例子裏,使用接口或是抽象類,是沒有多少區別的,惟一的區別僅僅是在 TypeScript 編譯成 JavaScript 後,interface 不存在了(不會有額外使用額外空間)。除此以外,抽象類能夠包含程序的實現細節,便是能夠實現代碼的複用。
abstract class Animal {
constructor(private name: string) {}
abstract eat(): void;
printName() {
console.log(this.name);
}
}
class Cat extends Animal {
constructor() {
super('cat');
}
eat() {}
}
class Dog extends Animal {
constructor() {
super('dog');
}
eat() {}
}
複製代碼
另外,接口和抽象類的另一個區別在於:抽象類和它的子類之間應該是通常和特殊的關係,而接口僅僅是類應該實現的一組規則。
現在,編程範式現存許多種:
每一個編程範式在本身所注重的場景裏發揮着舉足輕重的做用。好比 OOP 注重的是數據和行爲的打包封裝以及程序的接口和實現的解耦;而 FP 最大的特色是純函數的無狀態性質;面向過程則更貼近於實際工程中硬件的運行方式。
每一個範式都有它的「靈魂」,只有在實際使用時,才能理解。
在實際項目中,更多的時候,咱們是使用的多範式編程,正如範·羅伊信仰的同樣:解決一個編程問題,須要選擇正確的概念;解決多個問題,則須要組合分屬不一樣部分的多個概念。
更多文章,請關注公衆號: