溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用函數(shù)式思維重構(gòu)TypeScript 代碼

發(fā)布時(shí)間:2021-11-02 17:45:35 來(lái)源:億速云 閱讀:126 作者:柒染 欄目:web開(kāi)發(fā)

這篇文章給大家介紹如何使用函數(shù)式思維重構(gòu)TypeScript 代碼,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

談到函數(shù)式編程時(shí),我們常提到機(jī)制、方法,而不是核心原則。函數(shù)式編程不是關(guān)于 Monad、Monoid 和 Zipper  這些概念的,雖然它們確實(shí)很有用。從根本上來(lái)說(shuō),函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。本文是我在重構(gòu) TypeScript  代碼時(shí)使用函數(shù)式的一些思考的結(jié)果。

首先,我們需要用到以下幾項(xiàng)技術(shù):

  • 盡可能使用函數(shù)代替簡(jiǎn)單值

  • 數(shù)據(jù)轉(zhuǎn)換過(guò)程管道化

  • 提取通用函數(shù)

來(lái),開(kāi)始吧!

假設(shè)我們有兩個(gè)類,Employee 和 Department。Employee 有 name 和 salary 屬性,Department 只是  Employee 的簡(jiǎn)單集合。

class Employee {   constructor(public name: string, public salary: number) {} } class Department {   constructor(public employees: Employee[]) {}   works(employee: Employee): boolean {     return this.employees.indexOf(employee) > -1;   } }

我們要重構(gòu)的是 averageSalary 函數(shù)。

function averageSalary(employees: Employee[], minSalary: number, department?: Department): number {    let total = 0;    let count = 0;    employees.forEach((e) => {      if(minSalary <= e.salary && (department === undefined || department.works(e))){        total += e.salary;        count += 1;      }    });   return total === 0 ? 0 : total / count;

averageSalary 函數(shù)接收 employee 數(shù)組、***薪資 minSalary 以及可選的 department 作為參數(shù)。如果傳了  department 參數(shù),函數(shù)會(huì)計(jì)算該部門中所有員工的平均薪資;若不傳,則對(duì)全部員工進(jìn)行計(jì)算。

該函數(shù)的使用方式如下:

describe("average salary", () => {   const empls = [     new Employee("Jim", 100),     new Employee("John", 200),     new Employee("Liz", 120),     new Employee("Penny", 30)   ];   const sales = new Department([empls[0], empls[1]]);      it("calculates the average salary", () => {     expect(averageSalary(empls, 50, sales)).toEqual(150);     expect(averageSalary(empls, 50)).toEqual(140);   });

需求雖簡(jiǎn)單粗暴,可就算不提代碼難以拓展,其混亂是顯而易見(jiàn)的。若新增條件,函數(shù)簽名及接口就不得不發(fā)生變動(dòng),if 語(yǔ)句也會(huì)也越來(lái)越臃腫可怕。

我們一起用一些函數(shù)式編程的辦法重構(gòu)這個(gè)函數(shù)吧。

使用函數(shù)代替簡(jiǎn)單值

使用函數(shù)代替簡(jiǎn)單值看起來(lái)似乎不太直觀,但這卻是整理歸納代碼的強(qiáng)大辦法。在我們的例子中,這樣做,意味著要將 minSalary 和 department  參數(shù)替換成兩個(gè)條件檢驗(yàn)的函數(shù)。

type Predicate = (e: Employee) => boolean; function averageSalary(employees: Employee[], salaryCondition: Predicate,    departmentCondition?: Predicate): number {   let total = 0;   let count = 0;   employees.forEach((e) => {     if(salaryCondition(e) && (departmentCondition === undefined || departmentCondition(e))){       total += e.salary;       count += 1;     }   });   return total === 0 ? 0 : total / count; } // ... expect(averageSalary(empls, (e) => e.salary > 50, (e) => sales.works(e))).toEqual(150);

我們所做的就是將 salary、department  兩個(gè)條件接口統(tǒng)一起來(lái)。而此前這兩個(gè)條件是寫(xiě)死的,現(xiàn)在它們被明確定義了,并且遵循一致的接口。這次整合允許我們將所有條件作為數(shù)組傳遞。

function averageSalary(employees: Employee[], conditions: Predicate[]): number {   let total = 0;   let count = 0;   employees.forEach((e) => {     if(conditions.every(c => c(e))){       total += e.salary;       count += 1;     }   });   return (count === 0) ? 0 : total / count; } //... expect(averageSalary(empls, [(e) => e.salary > 50, (e) => sales.works(e)])).toEqual(150);

條件數(shù)組只不過(guò)是組合的條件,可以用一個(gè)簡(jiǎn)單的組合器將它們放到一起,這樣看起來(lái)更加明晰。

function and(predicates: Predicate[]): Predicate{   return (e) => predicates.every(p => p(e)); } function averageSalary(employees: Employee[], conditions: Predicate[]): number {   let total = 0;   let count = 0;   employees.forEach((e) => {     if(and(conditions)(e)){       total += e.salary;       count += 1;     }   });   return (count == 0) ? 0 : total / count; }

值得注意的是,“and” 組合器是通用的,可以復(fù)用并且還可能拓展為庫(kù)。

提起結(jié)果

現(xiàn)在,averageSalary 函數(shù)已健壯得多了。我們可以加入新條件,無(wú)需破壞函數(shù)接口或改變函數(shù)實(shí)現(xiàn)。

數(shù)據(jù)轉(zhuǎn)換過(guò)程管道化

函數(shù)式編程的另外一個(gè)很有用的實(shí)踐是將所有數(shù)據(jù)轉(zhuǎn)換過(guò)程變成管道。在本例中,就是將 filter 過(guò)程提取到循環(huán)外面。

function averageSalary(employees: Employee[], conditions: Predicate[]): number {   const filtered = employees.filter(and(conditions));   let total = 0   let count = 0   filtered.forEach((e) => {     total += e.salary;     count += 1;   });   return (count == 0) ? 0 : total / count; }

這樣一來(lái)計(jì)數(shù)的 count 就沒(méi)什么用了。

function averageSalary(employees: Employee[], conditions: Predicate[]): number{   const filtered = employees.filter(and(conditions));   let total = 0   filtered.forEach((e) => {     total += e.salary;   });   return (filtered.length == 0) ? 0 : total / filtered.length; }

接下來(lái),如在疊加之前將 salary 摘取出來(lái),求和過(guò)程就變成簡(jiǎn)單的 reduce 了。

function averageSalary(employees: Employee[], conditions: Predicate[]): number {   const filtered = employees.filter(and(conditions));   const salaries = filtered.map(e => e.salary);   const total = salaries.reduce((a,b) => a + b, 0);   return (salaries.length == 0) ? 0 : total / salaries.length; }

提取通用函數(shù)

接著我們發(fā)現(xiàn),***兩行代碼和當(dāng)前域完全沒(méi)什么關(guān)系。其中不包含任何與員工、部門相關(guān)的信息。僅僅只是一個(gè)計(jì)算平均數(shù)的函數(shù)。所以也將其提取出來(lái)。

function average(nums: number[]): number {   const total = nums.reduce((a,b) => a + b, 0);   return (nums.length == 0) ? 0 : total / nums.length; } function averageSalary(employees: Employee[], conditions: Predicate[]): number {   const filtered = employees.filter(and(conditions));   const salaries = filtered.map(e => e.salary);   return average(salaries); }

又一次,提取出的函數(shù)是完全通用的。

***,將所有 salary 部分提出來(lái)之后,我們得到***方案。

function employeeSalaries(employees: Employee[], conditions: Predicate[]): number[] {   const filtered = employees.filter(and(conditions));   return filtered.map(e => e.salary); } function averageSalary(employees: Employee[], conditions: Predicate[]): number {   return average(employeeSalaries(employees, conditions)); }

對(duì)比原始方案和***方案,我敢說(shuō),毫無(wú)疑問(wèn),后者更棒。首先,它更通用(我們可以不破壞函數(shù)接口的情況下添加新類型的判斷條件)。其次,我們從可變狀態(tài)(mutable  state)和 if 語(yǔ)句中解脫出來(lái),這使代碼更容易閱讀、理解。

何時(shí)收手

函數(shù)式風(fēng)格的編程中,我們會(huì)編寫(xiě)許多小型函數(shù),它們接收一個(gè)集合,返回新的集合。這些函數(shù)能夠以不同方式組合、復(fù)用 &mdash;&mdash;  棒極了。不過(guò),這種風(fēng)格的一個(gè)缺點(diǎn)是代碼可能會(huì)變得過(guò)度抽象,導(dǎo)致難以讀懂,那些函數(shù)組合在一起到底要干嘛?

我喜歡使用樂(lè)高來(lái)類比:樂(lè)高積木能夠以不同形式放在一起 &mdash;&mdash;  它們是可組合的。但注意,并不是所有積木都是一小塊。所以,在使用本文所述技巧進(jìn)行代碼重構(gòu)時(shí),千萬(wàn)別妄圖將一切都變成接收數(shù)組、返回?cái)?shù)組的函數(shù)。誠(chéng)然,這樣一些函數(shù)組合使用極度容易,可它們也會(huì)顯著降低我們對(duì)程序的理解能力。

本文展示了如何使用函數(shù)式思維重構(gòu) TypeScript 代碼。我所遵循的是以下幾點(diǎn)規(guī)則:

  • 盡可能使用函數(shù)代替簡(jiǎn)單值

  • 數(shù)據(jù)轉(zhuǎn)換過(guò)程管道化

  • 提取通用函數(shù)

關(guān)于如何使用函數(shù)式思維重構(gòu)TypeScript 代碼就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI