最近在過 《劍指Offer》 這本書上的題,儘可能把每題的多種解法都本身捋一遍,在過到 面試題20. 表示數值的字符串 這一題的時候,Discuss
裏有一個同窗提出了 職責鏈模式 的解法,讓人眼前一亮,另外一方面是筆者最近剛用 職責鏈模式解決了一些問題,因而決定用 JavaScript
重構一版題解,再熟悉熟悉這個設計模式。javascript
所謂職責鏈模式,是把要作的某一件事,交由一系列處理器依次處理,每一個處理器只完成本身的職責,一旦完成以後就交由下一個處理器處理。java
JavaScript
特點的職責鏈模式因爲 JavaScript
中 「函數是一等公民」的特性,能夠採起更函數式的寫法來取代類式寫法。面試
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"0123"及"-1E-16"都表示數值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是。設計模式
根據職責鏈模式的定義,咱們要作的能夠劃分爲如下這幾步:;函數
根據題意,咱們能夠很快地知道,主要是要劃分出整數校驗器、浮點數校驗器和科學計數法校驗器。接着要考慮特判狀況,因此還能夠細分出一個空串校驗器。考慮到字符串先後空格以及打頭的正負號是合法的,能夠分出空格的trimmer和正負號的trimmer。最後還須要一個兜底的校驗器,再通過以上全部校驗器校驗後,仍然不合法時返回false。接下里咱們一個一個實現便可。spa
這個是最好寫的,直接封裝一個函數返回 false
便可。設計
function falseValidator() { return false }
先從簡單的開始寫,空串校驗器,顧名思義,只要檢測出是空串,則直接返回 false
,不然交給下一個校驗器處理。code
/** * @param {string} value * @param {function} next */ function emptyValidator(value, next) { return value.trim().length !== 0 ? next(value) : false }
這個也比較簡單,遍歷字符串,發現其中一個字符不是數字時,交給下一個處理器處理,不然返回 true
orm
/** * @param {string} value * @param {function} next */ function integerValidator(value, next) { for (const char of value) { if (isNotNumber(char)) { return next(value) } } return true } /** * @param {string} value */ function isNotNumber(value) { return isNaN(value) || value === "" || value === " " }
從這裏開始就比較麻煩了,不過也有一個統一的思路,不管是浮點數仍是科學計數法,他們都有一個分隔符,咱們能夠採起相似二分法的思想,分割後再對左右兩個部分進行判斷便可。ip
/** * @param {string} value * @param {function} next */ function floatValidator(value, next) { const pos = value.indexOf(".") if (pos === -1) { return next(value) } const left = value.substring(0, pos) const right = value.substring(pos + 1) // 處理 .xxx 的狀況 if (left === "") { if (partFloatValidator(right)) { return true } return next(value) } // 處理 xxx. 的狀況 if (right === "") { if (partFloatValidator(left)) { return true } return next(value) } if (partFloatValidator(left) && partFloatValidator(right)) { return true } return next(value) }
這裏要注意 xxx.
和 .xxx
,這兩個特殊狀況也是合法的。接下里二分以後也有一個處理器須要咱們寫,並且能夠發現,這個處理器是一個複合處理器,因此咱們能夠先把驅動用的高級函數和須要的簡單處理器都寫出來了。
const partFloatValidator = process([ emptyValidator, headTailIntegerValidator, integerValidator, falseValidator, ]) /** * @param {function[]} validators */ function process(validators) { return validators.reduceRight((next, validate) => (data) => validate(data, next) ) } /** * @param {string} value * @param {function} next */ function headTailIntegerValidator(value, next) { if (isNotNumber(value[0]) || isNotNumber(value[value.length - 1])) { return false } return next(value) }
具體思路和浮點數校驗器相同。
const leftpartSienceFormatValidator = process([ emptyValidator, signTrimmer, headTailSpaceValidator, emptyValidator, integerValidator, floatValidator, falseValidator, ]) const rightpartSienceFormatValidator = process([ emptyValidator, signTrimmer, headTailSpaceValidator, emptyValidator, integerValidator, falseValidator, ]) /** * @param {string} value * @param {function} next */ function headTailSpaceValidator(value, next) { if (value.startsWith(" ") || value.endsWith(" ")) { return false } return next(value) } /** * @param {string} value * @param {function} next */ function spaceTrimmer(value, next) { return next(value.trim()) } /** * @param {string} value * @param {function} next */ function signTrimmer(value, next) { if (value.startsWith("+") || value.startsWith("-")) { value = value.substring(1) } return next(value) } /** * @param {string} value * @param {function} next */ function sienceFormatValidator(value, next) { value = value.toLowerCase() const pos = value.indexOf("e") if (pos === -1) { return next(value) } const left = value.substring(0, pos) const right = value.substring(pos + 1) if ( leftpartSienceFormatValidator(left) && rightpartSienceFormatValidator(right) ) { return true } return next(value) }
經過寫上述的兩個複合校驗器,最終的校驗器也是一樣的道理,只要按正確的斷定順序複合上述校驗器便可。
const isNumber = process([ emptyValidator, spaceTrimmer, signTrimmer, integerValidator, floatValidator, sienceFormatValidator, falseValidator, ])
運用設計模式,雖然多寫了不少代碼,可是拓展性和可維護性是不可忽視的,缺點就是運行效率比較低,不過做爲一種新思路,筆者仍是很喜歡的。