摘要:為什么要面向對象你需要知道的面向對象面向對象并不是針對一種特定的語言,而是一種編程范式。后端傳遞過來顯示工人完成狀態(tài)的字段代表未完成,代表已完成。其實這就是如何消除代碼副作用的問題將副作用隔離。
為什么要面向對象? 你需要知道的面向對象
面向對象并不是針對一種特定的語言,而是一種編程范式。但是每種語言在設計之初,都會強烈地支持某種編程范式,比如面向對象的Java,而Javascript并不是強烈地支持面向對象。
什么時候需要面向對象?任何一名開發(fā)人員,在編寫具體的代碼的時候,不應該為了套用某種編程范式,而去編寫代碼和改造代碼。任何編寫方式的目的是:
讓代碼邏輯清晰
可讀性良好
沒有冗余代碼
前端編寫過程中什么時候需要面向對象?在我的日常工作中,最不想做的的就是兩點:
復制粘貼代碼
不同的代碼中具備相同的邏輯或者變量
因為這兩種方式,會讓代碼冗余,而且不易維護。為什么?
因為相同的代碼,具備相同的邏輯,也就是具備相同的業(yè)務邏輯場景,如果場景一旦改變,你將會改變兩處代碼。
ok,到這里,我們來講一個具體的業(yè)務場景。
場景1: 前端需要顯示工人的工作完成狀態(tài),如果已經完成了,前端提供一個查看詳情的入口,如果沒有完成,提供工人去完成任務的入口。后端傳遞過來顯示工人完成狀態(tài)的字段:user_done_status:0,代表未完成,1代表已完成。前端需要實現這樣一個表格:
工人名字 | 完成狀態(tài) | 操作 |
---|---|---|
小王 | 已完成 | 查看詳情 |
老王 | 未完成 | 去完成 |
// status.js // 1:需要一個狀態(tài)映射表,來實現第二列的功能 export const statusMap = new Map([ [0, "未完成"], [1, "已完成"] ]); // 2: 需要一個動作映射表,來實現第三列的功能 export const actionMap = new Map([ [0, "查看詳情"], [1, "去完成"] ]); // 3: 需要一個狀態(tài)判讀函數,來實現第三列的功能 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; } } }
至于第三個為什么這么寫,可以看一下這篇文章
階段二:壞代碼的味道上面的三段代碼多帶帶寫出來沒啥問題,看看下面的可能問題就出來,這相當于實現了三個函數,那么需要在顯示在表格中就需要這樣寫:
import { statusMap, actionMap, getUserAction } from "./status.js" .... .... // 第二列 return ( { statusMap.get(status) } ); // 第三列 return ( getUserAction(status)}> actionMap.get(status) );
這樣的寫法,看起來沒啥問題,但是可讀性是很差的,主要體現在兩點:
三個函數都和status相關,但是展現形式上是割裂的
每個函數都需要傳遞一個status
可能有的人會說,這樣把上面的代碼多帶帶抽離出一個文件,也沒什么問題,狀態(tài)也是比較集中的,嗯,這種說法也沒什么問題,多帶帶提取一個文件,用作處理用的狀態(tài),是一種常見的抽象方法。但是可能會遇到下面集中情況,就會讓你很難受:
后端改了下字段,那么你就需要在階段二中的第二列和第三列中傳入參數的地方修改對應的字段名字(估計想宰了rd吧)
業(yè)務場景變化,工人的任務狀態(tài),添加了其他限制,比如任務的時間限制,任務有未開始、進行中、已過期三種狀態(tài),只有當在任務進行中的時候,才可以展示用戶的狀態(tài),否則就展示未開始或者已過期,總結起來,需要下面的幾種狀態(tài):
未開始
已完成/未完成
已過期
那么顯然,你就需要修改代碼的邏輯,僅僅依靠一個statusMap就不能行了。當然這里有人說了,那我把map編程一個函數:
const getUserStatus = (status, startTime, endTime) => { // ...do something }
這樣是不是就可以了,嗯,說的也沒什么問題,那你需要去修改之前寫的所有代碼,傳入不同的參數,就算一開始你用的不是map而是函數,那么你的代碼也需要再傳入兩個多余的參數,start_time和end_time。
需要解決的痛點:展現形式的分離,需要一種集中的狀態(tài)處理
需要傳入多個參數進行判斷,業(yè)務場景的變化或者字段的變化,都需要多處修改代碼
最開始遇到這來那個問題的時候,我想的是怎么樣能夠把所有的處理集中到一起,自然而然就想到了面向對象,將用戶的狀態(tài)作為一個對象,對象具備特定的屬性和對應的操作行為。
Javascript中如何編寫面向對象的代碼?先睹為快,我們看一下,上面的代碼在面向對象的寫法,直接使用es6的class
上面業(yè)務場景的面向對象的寫法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, "未開始"], [1, "已過期"] ]); 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); } } ... ... // 省略其他的了 }
那么寫好了上面的類,我們應該在其他地方怎么引用呢?
// 第一步:直接講后端傳過來的信息,構造一個新的對象
const userInfo = new UserStatus(info);
// 第二步:直接調用對應的方法或者參數
return (
{
userInfo.userStatus
}
);
以后無論業(yè)務場景如何改變這部分代碼都不需要重新改寫,只需要改寫對應的類的操作就可以了。
這樣看了比較干凈的是具體的view層代碼,就是簡單的html和對應的數據,沒有其他操作。其實這就是如何消除代碼副作用的問題:將副作用隔離。當你把所有的副作用隔離之后,代碼看起來干凈許多,你像redux-saga就是將對應的異步操作隔離出來。
ok,看了上面的類的寫法,我們來看一下面向對象的寫法應該要怎么寫:
面向對象 面向對象的三大特性封裝
繼承
多態(tài)
特性 | 特點 | 舉例 |
---|---|---|
封裝 | 封裝就是對具體的屬性和實現細節(jié)進行隱藏,形成統(tǒng)一的的整體對外部提供對應的接口 | 上面的例子就是很好的解釋 |
繼承 | 繼承就是子類可以繼承父類的屬性和行為,也可以重寫父類的行為 | 比如工人有用戶狀態(tài),老板也有用戶狀態(tài),他們都可以繼承UserStatus這一個基類 |
多態(tài) | 同一個行為在在不同的調用方式下,具備不同的行為,依賴于抽象和重寫 | 比如工人和老板都具備一個行為那就是吃飯,工人吃的是饅頭,老板吃的是海鮮,同樣是吃這個行為,產生了不同的表現形式 |
在基本的面向對象中有幾個原則SOLID原則,但是這里我不想詳細寫了,想說一下,我在封裝對象的時候會注重的幾個方面
基類與具體數據無關,只封裝了特定的行為和屬性,基類只注重抽象公共的部分
類的行為對擴展是開放的,但是對于修改是不開放的(開放封閉原則),像上面的寫法是存在風險的,因為生成的對象實例中的屬性可以被隨意的修改,我加了_,就是防止這種行為,但是最好的方式應該是使用get/set方法來對屬性限制操作;對于對象的屬性,一定要明確,因為js中一個是沒有類型的限制不要出現下面的寫法:
class Base { constructor(props) { for (let key of props) { this[key] = props[key]; } } }
一個類只應該依賴于他繼承的類,不能依賴于其他類,這樣能最大限度地減少耦合
注意的問題注意??在js中一定小小心this的使用,假設有一個初始類:
初始類:
class Base { constructor(props) { this._a = props.a; } status() { return this._a; } }
避免下面的行為:
// 方式1: let { status } = new Base({a: 678}); status() // 會報錯
而應該使用下面的寫法:
//方式2: let info = new Base({a: 678}); info.status(); //輸出正確
根本原因就是this在作怪,第一種this指向了全局作用域。
最后也是最重要的上面的面向對象主要解決了前文提到的兩個痛點,但是也不是所有的業(yè)務場景都適合面向對象,當你的代碼出現了一些壞味道(代碼容易、代碼分散不易處理),可以考慮下面向對象,畢竟適合的才是最好的
參考資料面向對象封裝的五個原則)
五個原則比較形象的解釋
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/106953.html
摘要:函數式編程的哲學就是假定副作用是造成不正當行為的主要原因。函數組合面向對象通常被比喻為名詞,而函數式編程是動詞。尾遞歸優(yōu)化函數式編程語言中因為不可變數據結構的原因,沒辦法實現循環(huán)。 零、前言 說到函數式編程,想必各位或多或少都有所耳聞,然而對于函數式的內涵和本質可能又有些說不清楚。 所以本文希望針對工程師,從應用(而非學術)的角度將函數式編程相關思想和實踐(以 JavaScript 為...
摘要:它大致概述并討論了前端工程的實踐如何學習它,以及在年實踐時使用什么工具。目的是每年發(fā)布一次內容更新。前端實踐第一部分廣泛描述了前端工程的實踐。對大多數人來說,函數式編程看起來更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來了解前端開發(fā)實踐的指南。它大致概述并...
摘要:它大致概述并討論了前端工程的實踐如何學習它,以及在年實踐時使用什么工具。目的是每年發(fā)布一次內容更新。前端實踐第一部分廣泛描述了前端工程的實踐。對大多數人來說,函數式編程看起來更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來了解前端開發(fā)實踐的指南。它大致概述并...
摘要:它大致概述并討論了前端工程的實踐如何學習它,以及在年實踐時使用什么工具。目的是每年發(fā)布一次內容更新。前端實踐第一部分廣泛描述了前端工程的實踐。對大多數人來說,函數式編程看起來更加自然。 1 Front-End Developer Handbook 2017 地址:https://frontendmasters.com/b... 這是任何人都可以用來了解前端開發(fā)實踐的指南。它大致概述并...
摘要:聲明式編程一種編程范式,與命令式編程相對立。常見的聲明式編程語言有數據庫查詢語言,正則表達式邏輯編程函數式編程組態(tài)管理系統(tǒng)等。函數式編程,特別是純函數式編程,嘗試最小化狀態(tài)帶來的副作用,因此被認為是聲明式的。 編程范式與函數式編程 一、編程范式的分類 常見的編程范式有:函數式編程、程序編程、面向對象編程、指令式編程等。在面向對象編程的世界,程序是一系列相互作用(方法)的對象(Class...
閱讀 1246·2021-11-24 09:39
閱讀 2756·2021-09-28 09:35
閱讀 1137·2019-08-30 15:55
閱讀 1437·2019-08-30 15:44
閱讀 941·2019-08-29 17:00
閱讀 2041·2019-08-29 12:19
閱讀 3367·2019-08-28 18:28
閱讀 756·2019-08-28 18:10