摘要:為什么要面向?qū)ο竽阈枰赖拿嫦驅(qū)ο竺嫦驅(qū)ο蟛⒉皇轻槍?duì)一種特定的語(yǔ)言,而是一種編程范式。后端傳遞過(guò)來(lái)顯示工人完成狀態(tài)的字段代表未完成,代表已完成。其實(shí)這就是如何消除代碼副作用的問(wèn)題將副作用隔離。
為什么要面向?qū)ο螅?/b> 你需要知道的面向?qū)ο?/b>
面向?qū)ο蟛⒉皇轻槍?duì)一種特定的語(yǔ)言,而是一種編程范式。但是每種語(yǔ)言在設(shè)計(jì)之初,都會(huì)強(qiáng)烈地支持某種編程范式,比如面向?qū)ο蟮腏ava,而Javascript并不是強(qiáng)烈地支持面向?qū)ο蟆?/p> 什么時(shí)候需要面向?qū)ο螅?/b>
任何一名開(kāi)發(fā)人員,在編寫(xiě)具體的代碼的時(shí)候,不應(yīng)該為了套用某種編程范式,而去編寫(xiě)代碼和改造代碼。任何編寫(xiě)方式的目的是:
讓代碼邏輯清晰
可讀性良好
沒(méi)有冗余代碼
前端編寫(xiě)過(guò)程中什么時(shí)候需要面向?qū)ο螅?/b>在我的日常工作中,最不想做的的就是兩點(diǎn):
復(fù)制粘貼代碼
不同的代碼中具備相同的邏輯或者變量
因?yàn)檫@兩種方式,會(huì)讓代碼冗余,而且不易維護(hù)。為什么?
因?yàn)橄嗤拇a,具備相同的邏輯,也就是具備相同的業(yè)務(wù)邏輯場(chǎng)景,如果場(chǎng)景一旦改變,你將會(huì)改變兩處代碼。
ok,到這里,我們來(lái)講一個(gè)具體的業(yè)務(wù)場(chǎng)景。
場(chǎng)景1: 前端需要顯示工人的工作完成狀態(tài),如果已經(jīng)完成了,前端提供一個(gè)查看詳情的入口,如果沒(méi)有完成,提供工人去完成任務(wù)的入口。后端傳遞過(guò)來(lái)顯示工人完成狀態(tài)的字段:user_done_status:0,代表未完成,1代表已完成。前端需要實(shí)現(xiàn)這樣一個(gè)表格:
工人名字 | 完成狀態(tài) | 操作 |
---|---|---|
小王 | 已完成 | 查看詳情 |
老王 | 未完成 | 去完成 |
// status.js // 1:需要一個(gè)狀態(tài)映射表,來(lái)實(shí)現(xiàn)第二列的功能 export const statusMap = new Map([ [0, "未完成"], [1, "已完成"] ]); // 2: 需要一個(gè)動(dòng)作映射表,來(lái)實(shí)現(xiàn)第三列的功能 export const actionMap = new Map([ [0, "查看詳情"], [1, "去完成"] ]); // 3: 需要一個(gè)狀態(tài)判讀函數(shù),來(lái)實(shí)現(xiàn)第三列的功能 function isUserDone(status) { return +status === 1; } const actionMap = new Map([ [status => isUserDone(status), userCanCheckResult], [status => !isUserDone(status), needUserToCompoleteWork] ]); function handleClick() { for (let [done, action] of actionMap) { if (done()) { actionMap(); return; } } }
至于第三個(gè)為什么這么寫(xiě),可以看一下這篇文章
階段二:壞代碼的味道上面的三段代碼多帶帶寫(xiě)出來(lái)沒(méi)啥問(wèn)題,看看下面的可能問(wèn)題就出來(lái),這相當(dāng)于實(shí)現(xiàn)了三個(gè)函數(shù),那么需要在顯示在表格中就需要這樣寫(xiě):
import { statusMap, actionMap, getUserAction } from "./status.js" .... .... // 第二列 return ( { statusMap.get(status) } ); // 第三列 return ( getUserAction(status)}> actionMap.get(status) );
這樣的寫(xiě)法,看起來(lái)沒(méi)啥問(wèn)題,但是可讀性是很差的,主要體現(xiàn)在兩點(diǎn):
三個(gè)函數(shù)都和status相關(guān),但是展現(xiàn)形式上是割裂的
每個(gè)函數(shù)都需要傳遞一個(gè)status
可能有的人會(huì)說(shuō),這樣把上面的代碼多帶帶抽離出一個(gè)文件,也沒(méi)什么問(wèn)題,狀態(tài)也是比較集中的,嗯,這種說(shuō)法也沒(méi)什么問(wèn)題,多帶帶提取一個(gè)文件,用作處理用的狀態(tài),是一種常見(jiàn)的抽象方法。但是可能會(huì)遇到下面集中情況,就會(huì)讓你很難受:
后端改了下字段,那么你就需要在階段二中的第二列和第三列中傳入?yún)?shù)的地方修改對(duì)應(yīng)的字段名字(估計(jì)想宰了rd吧)
業(yè)務(wù)場(chǎng)景變化,工人的任務(wù)狀態(tài),添加了其他限制,比如任務(wù)的時(shí)間限制,任務(wù)有未開(kāi)始、進(jìn)行中、已過(guò)期三種狀態(tài),只有當(dāng)在任務(wù)進(jìn)行中的時(shí)候,才可以展示用戶的狀態(tài),否則就展示未開(kāi)始或者已過(guò)期,總結(jié)起來(lái),需要下面的幾種狀態(tài):
未開(kāi)始
已完成/未完成
已過(guò)期
那么顯然,你就需要修改代碼的邏輯,僅僅依靠一個(gè)statusMap就不能行了。當(dāng)然這里有人說(shuō)了,那我把map編程一個(gè)函數(shù):
const getUserStatus = (status, startTime, endTime) => { // ...do something }
這樣是不是就可以了,嗯,說(shuō)的也沒(méi)什么問(wèn)題,那你需要去修改之前寫(xiě)的所有代碼,傳入不同的參數(shù),就算一開(kāi)始你用的不是map而是函數(shù),那么你的代碼也需要再傳入兩個(gè)多余的參數(shù),start_time和end_time。
需要解決的痛點(diǎn):展現(xiàn)形式的分離,需要一種集中的狀態(tài)處理
需要傳入多個(gè)參數(shù)進(jìn)行判斷,業(yè)務(wù)場(chǎng)景的變化或者字段的變化,都需要多處修改代碼
最開(kāi)始遇到這來(lái)那個(gè)問(wèn)題的時(shí)候,我想的是怎么樣能夠把所有的處理集中到一起,自然而然就想到了面向?qū)ο螅瑢?strong>用戶的狀態(tài)作為一個(gè)對(duì)象,對(duì)象具備特定的屬性和對(duì)應(yīng)的操作行為。
Javascript中如何編寫(xiě)面向?qū)ο蟮拇a?先睹為快,我們看一下,上面的代碼在面向?qū)ο蟮膶?xiě)法,直接使用es6的class
上面業(yè)務(wù)場(chǎng)景的面向?qū)ο蟮膶?xiě)法import moment from "moment"; class UserStatus { constructor(props) { const keys = [ user_done_status, start_time, end_time ] ; for (let key of keys) { this.[`_${key}] = (props || {})[key]; } } static StatusMap = new Map([ [0, "未完成"], [1, "已完成"] ]); static TimeMap = newMap([ [0, "未開(kāi)始"], [1, "已過(guò)期"] ]); get userDoneStatus () { return this._user_done_status; } get isInWorkingTime() { const now = new Date(); return moment(now).isBetween(moment(this._start_time), moment(this._end_time)); } get isWorkStart() { const now = new Date(); return moment(now).isAfter(moment(now)); } get userStatus () { if (this.isInWorkingTime) { return UserStatus.StatusMap.get(this.userDoneStatus); } else { return UserStatus.TimeMap.get(+this.isWorkStart); } } ... ... // 省略其他的了 }
那么寫(xiě)好了上面的類,我們應(yīng)該在其他地方怎么引用呢?
// 第一步:直接講后端傳過(guò)來(lái)的信息,構(gòu)造一個(gè)新的對(duì)象
const userInfo = new UserStatus(info);
// 第二步:直接調(diào)用對(duì)應(yīng)的方法或者參數(shù)
return (
{
userInfo.userStatus
}
);
以后無(wú)論業(yè)務(wù)場(chǎng)景如何改變這部分代碼都不需要重新改寫(xiě),只需要改寫(xiě)對(duì)應(yīng)的類的操作就可以了。
這樣看了比較干凈的是具體的view層代碼,就是簡(jiǎn)單的html和對(duì)應(yīng)的數(shù)據(jù),沒(méi)有其他操作。其實(shí)這就是如何消除代碼副作用的問(wèn)題:將副作用隔離。當(dāng)你把所有的副作用隔離之后,代碼看起來(lái)干凈許多,你像redux-saga就是將對(duì)應(yīng)的異步操作隔離出來(lái)。
ok,看了上面的類的寫(xiě)法,我們來(lái)看一下面向?qū)ο蟮膶?xiě)法應(yīng)該要怎么寫(xiě):
面向?qū)ο?/b> 面向?qū)ο蟮娜筇匦?/b>封裝
繼承
多態(tài)
特性 | 特點(diǎn) | 舉例 |
---|---|---|
封裝 | 封裝就是對(duì)具體的屬性和實(shí)現(xiàn)細(xì)節(jié)進(jìn)行隱藏,形成統(tǒng)一的的整體對(duì)外部提供對(duì)應(yīng)的接口 | 上面的例子就是很好的解釋 |
繼承 | 繼承就是子類可以繼承父類的屬性和行為,也可以重寫(xiě)父類的行為 | 比如工人有用戶狀態(tài),老板也有用戶狀態(tài),他們都可以繼承UserStatus這一個(gè)基類 |
多態(tài) | 同一個(gè)行為在在不同的調(diào)用方式下,具備不同的行為,依賴于抽象和重寫(xiě) | 比如工人和老板都具備一個(gè)行為那就是吃飯,工人吃的是饅頭,老板吃的是海鮮,同樣是吃這個(gè)行為,產(chǎn)生了不同的表現(xiàn)形式 |
在基本的面向?qū)ο笾杏袔讉€(gè)原則SOLID原則,但是這里我不想詳細(xì)寫(xiě)了,想說(shuō)一下,我在封裝對(duì)象的時(shí)候會(huì)注重的幾個(gè)方面
基類與具體數(shù)據(jù)無(wú)關(guān),只封裝了特定的行為和屬性,基類只注重抽象公共的部分
類的行為對(duì)擴(kuò)展是開(kāi)放的,但是對(duì)于修改是不開(kāi)放的(開(kāi)放封閉原則),像上面的寫(xiě)法是存在風(fēng)險(xiǎn)的,因?yàn)樯傻膶?duì)象實(shí)例中的屬性可以被隨意的修改,我加了_,就是防止這種行為,但是最好的方式應(yīng)該是使用get/set方法來(lái)對(duì)屬性限制操作;對(duì)于對(duì)象的屬性,一定要明確,因?yàn)閖s中一個(gè)是沒(méi)有類型的限制不要出現(xiàn)下面的寫(xiě)法:
class Base { constructor(props) { for (let key of props) { this[key] = props[key]; } } }
一個(gè)類只應(yīng)該依賴于他繼承的類,不能依賴于其他類,這樣能最大限度地減少耦合
注意的問(wèn)題注意??在js中一定小小心this的使用,假設(shè)有一個(gè)初始類:
初始類:
class Base { constructor(props) { this._a = props.a; } status() { return this._a; } }
避免下面的行為:
// 方式1: let { status } = new Base({a: 678}); status() // 會(huì)報(bào)錯(cuò)
而應(yīng)該使用下面的寫(xiě)法:
//方式2: let info = new Base({a: 678}); info.status(); //輸出正確
根本原因就是this在作怪,第一種this指向了全局作用域。
最后也是最重要的上面的面向?qū)ο笾饕鉀Q了前文提到的兩個(gè)痛點(diǎn),但是也不是所有的業(yè)務(wù)場(chǎng)景都適合面向?qū)ο?/strong>,當(dāng)你的代碼出現(xiàn)了一些壞味道(代碼容易、代碼分散不易處理),可以考慮下面向?qū)ο螅吘?strong>適合的才是最好的
參考資料面向?qū)ο蠓庋b的五個(gè)原則)
五個(gè)原則比較形象的解釋
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/106953.html
摘要:函數(shù)式編程的哲學(xué)就是假定副作用是造成不正當(dāng)行為的主要原因。函數(shù)組合面向?qū)ο笸ǔ1槐扔鳛槊~,而函數(shù)式編程是動(dòng)詞。尾遞歸優(yōu)化函數(shù)式編程語(yǔ)言中因?yàn)椴豢勺償?shù)據(jù)結(jié)構(gòu)的原因,沒(méi)辦法實(shí)現(xiàn)循環(huán)。 零、前言 說(shuō)到函數(shù)式編程,想必各位或多或少都有所耳聞,然而對(duì)于函數(shù)式的內(nèi)涵和本質(zhì)可能又有些說(shuō)不清楚。 所以本文希望針對(duì)工程師,從應(yīng)用(而非學(xué)術(shù))的角度將函數(shù)式編程相關(guān)思想和實(shí)踐(以 JavaScript 為...
摘要:它大致概述并討論了前端工程的實(shí)踐如何學(xué)習(xí)它,以及在年實(shí)踐時(shí)使用什么工具。目的是每年發(fā)布一次內(nèi)容更新。前端實(shí)踐第一部分廣泛描述了前端工程的實(shí)踐。對(duì)大多數(shù)人來(lái)說(shuō),函數(shù)式編程看起來(lái)更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來(lái)了解前端開(kāi)發(fā)實(shí)踐的指南。它大致概述并...
摘要:它大致概述并討論了前端工程的實(shí)踐如何學(xué)習(xí)它,以及在年實(shí)踐時(shí)使用什么工具。目的是每年發(fā)布一次內(nèi)容更新。前端實(shí)踐第一部分廣泛描述了前端工程的實(shí)踐。對(duì)大多數(shù)人來(lái)說(shuō),函數(shù)式編程看起來(lái)更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來(lái)了解前端開(kāi)發(fā)實(shí)踐的指南。它大致概述并...
摘要:它大致概述并討論了前端工程的實(shí)踐如何學(xué)習(xí)它,以及在年實(shí)踐時(shí)使用什么工具。目的是每年發(fā)布一次內(nèi)容更新。前端實(shí)踐第一部分廣泛描述了前端工程的實(shí)踐。對(duì)大多數(shù)人來(lái)說(shuō),函數(shù)式編程看起來(lái)更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來(lái)了解前端開(kāi)發(fā)實(shí)踐的指南。它大致概述并...
摘要:聲明式編程一種編程范式,與命令式編程相對(duì)立。常見(jiàn)的聲明式編程語(yǔ)言有數(shù)據(jù)庫(kù)查詢語(yǔ)言,正則表達(dá)式邏輯編程函數(shù)式編程組態(tài)管理系統(tǒng)等。函數(shù)式編程,特別是純函數(shù)式編程,嘗試最小化狀態(tài)帶來(lái)的副作用,因此被認(rèn)為是聲明式的。 編程范式與函數(shù)式編程 一、編程范式的分類 常見(jiàn)的編程范式有:函數(shù)式編程、程序編程、面向?qū)ο缶幊獭⒅噶钍骄幊痰取T诿嫦驅(qū)ο缶幊痰氖澜纾绦蚴且幌盗邢嗷プ饔茫ǚ椒ǎ┑膶?duì)象(Class...
閱讀 1172·2021-11-24 09:39
閱讀 2675·2021-09-28 09:35
閱讀 1070·2019-08-30 15:55
閱讀 1361·2019-08-30 15:44
閱讀 880·2019-08-29 17:00
閱讀 1969·2019-08-29 12:19
閱讀 3311·2019-08-28 18:28
閱讀 690·2019-08-28 18:10