Character Specifications for A Word
序言
筆者有幸參加了一次Code Retreat活動,整個過程很有收穫,本文透過Golang語言來回放一下。
需求一:判斷某個單詞是否包含數字
這個需求比較簡單,程式碼實現如下:
func HasDigit(word string) bool { for _, c := range word { if unicode.IsDigit(c) { return true } } return false}
需求二:判斷某個單詞是否包含大寫字母
有了需求一的基礎後,可以透過copy-paste快速實現需求二:
func HasDigit(word string) bool { for _, c := range word { if unicode.IsDigit(c) { return true } } return false}func HasUpper(word string) bool { for _, c := range str { if unicode.IsUpper(c) { return true } } return false}
很明顯,HasDigit函式和HasUpper函式除過if的條件判斷外,其餘程式碼都一樣,所以我們使用抽象這個強大的屠龍刀來消除重複:
定義一個介面CharSpec,作為所有字元謂詞的抽象,方法Satisfy用來判斷謂詞是否為真
針對需求一定義具有原子語義的謂詞IsDigit
針對需求二定義具有原子語義的謂詞IsUpper
謂詞相關程式碼實現如下:
type CharSpec interface { Satisfy(c rune) bool}type IsDigit struct { }func (i IsDigit) Satisfy(c rune) bool { return unicode.IsDigit(c) }type IsUpper struct { }func (i IsUpper) Satisfy(c rune) bool { return unicode.IsUpper(c) }
要完成需求,還必須將謂詞注入給單詞的Has語義函式,而Exists具有Has語義,同時表達力很強:
func Exists(word string, spec CharSpec) bool { for _, c := range word { if spec.Satisfy(c) { return true } } return false}
透過Exists判斷某個單詞word是否包含數字:
isDigit := IsDigit{} ok := Exists(word, isDigit) ...
透過Exists判斷某個單詞word是否包含大寫字母:
isUpper := IsUpper{} ok := Exists(word, isUpper) ...
其實需求二的故事還沒講完:)
對於普通的程式設計師來說,能完成上面的程式碼已經很好了,而對於經驗豐富的程式設計師來說,在需求一剛完成後可能就發現了新的變化方向,即單詞的Has語義和字元的Is語義是兩個不同的變化方向,所以在需求二開始前就透過重構分離了變化方向:
type IsDigit struct { }func (i IsDigit) Satisfy(c rune) bool { return unicode.IsDigit(c) }func Exists(word string, spec IsDigit) bool { for _, c := range word { if spec.Satisfy(c) { return true } } return false}
透過Exists判斷某個單詞word是否包含數字:
isDigit := IsDigit{} ok := Exists(word, isDigit) ...
在需求二出來後,謂詞被第一顆子彈擊中,我們根據Uncle Bob的建議,應用開放封閉原則,於是也就寫出了普通程式設計師在需求二中消除重複後的程式碼。
殊途同歸,這並不是巧合,而是有理論依據。
我們一起回顧一下 袁英傑先生 提出的:
一個變化導致多處修改:消除重複
多個變化導致一處修改:分離不同的變化方向
不依賴不必要的依賴:縮小依賴範圍
不依賴不穩定的依賴:向著穩定的方向依賴
這四個原則的提出是針對簡單設計四原則中的第二條“消除重複”,使得目標的達成有章可循。我們應用正交設計四原則,可以將系統分解成很多單一職責的小類(也有一些小函式),然後再將它們根據需要而靈活的組合起來。
細細品味正交設計四原則,你就會發現:第一條是被動策略,而後三條是主動策略。這就是說,第一條是一種事後補救的策略,而後三條是一種事前預防的策略,目標都是為了消除重複。
從上面的分析可以看出,普通的程式設計師習慣使用被動策略,而經驗豐富的程式設計師更喜歡使用主動策略。Anyway,他們殊途同歸,都消除了重複。
需求三:判斷某個單詞是否包含_
不管是包含下劃線還是中劃線,都有原子語義Equals,我們將程式碼快速實現:
type Equals struct { c rune}func (e Equals) Satisfy(c rune) bool { return c == e.c }
透過Exists判斷某個單詞word是否包含_:
isUnderline := Equals{'_'} ok := Exists(word, isUnderline) ...
需求四:判斷某個單詞是否不包含_
字母是下劃線的謂詞是Equals,那麼字母不是下劃線的謂詞就是給Equals前增加一個修飾語義Not,Not修飾謂詞後是一個新的謂詞,程式碼實現如下:
type Not struct { spec CharSpec } fun (n Not) Satisfy(c rune) bool { return !n.spec.Satisfy(c) }
單詞不包含下劃線,就不是Exists語義了,而是ForAll語義,程式碼實現如下:
func ForAll(word string, spec CharSpec) bool { for _, c := range str { if !spec.Satisfy(c) { return false } } return true}
透過ForAll判斷某個單詞word是否不包含_:
isNotUnderline := Not{Equals{'_'}} ok := ForAll(word, isNotUnderline) ...
功能雖然實現了,但是我們發現Exists函式和ForAll函式有很多程式碼是重複的,使用重構基本手法Extract Method:
func expect(word string, spec CharSpec, ok bool) bool { for _, c := range word { if spec.Satisfy(c) == ok { return ok } } return !ok }func Exists(word string, spec CharSpec) bool { return expect(word, spec, true) }func ForAll(word string, spec CharSpec) bool { return expect(word, spec, false) }
需求五:判斷某個單詞是否包含_或者*
字母是x或y的謂詞具有組合語義AnyOf,其中x為Equals{'_'},y為Equals{'*'},程式碼實現如下:
type AnyOf struct { specs []CharSpec }func (a AnyOf) Satisfy(c rune) bool { for _, spec := range a.specs { if spec.Satisfy(c) { return true } } return false}
透過Exists判斷某個單詞word是否包含_或*:
isUnderlineOrStar := AnyOf{[]CharSpec{Equals{'_'}, Equals{'*'}}} ok := Exists(word, isUnderlineOrStar) ...
需求六:判斷某個單詞是否包含空白符,但除去空格
空白符包括空格、製表符和換行符等,具體見下面程式碼:
func IsSpace(r rune) bool { // This property isn't the same as Z; special-case it. if uint32(r)字母是空白符的謂詞還沒有實現,我們定義具有原子語義的謂詞IsSpace:
type IsSpace struct { }func (i IsSpace) Satisfy(c rune) bool { return unicode.IsSpace(c) }字母是x和y的謂詞具有組合語義AllOf,其中x為IsSpace,y為Not{Equals{' '}},程式碼實現如下:
type AllOf struct { specs []CharSpec }func (a AllOf) Satisfy(c rune) bool { for _, spec := range a.specs { if !spec.Satisfy(c) { return false } } return true}透過Exists判斷某個單詞word是否包含空白符,但除去空格:
isSpaceAndNotBlank := AllOf{[]CharSpec{IsSpace{}, Not{Equals{' '}}}} ok := Exists(word, isSpaceAndNotBlank) ...需求七:判斷某個單詞是否包含字母x,且不區分大小寫
不區分大小寫是一種具有修飾語義的謂詞IgnoreCase,程式碼實現如下:
type IgnoreCase struct { spec CharSpec }func (i IgnoreCase) Satisfy(c rune) bool { return i.spec.Satisfy(unicode.ToLower(c)) }透過Exists判斷某個單詞word是否包含字母x,且不區分大小寫:
isXIgnoreCase := IgnoreCase{Equals{'x'}} ok := Exists(word, isXIgnoreCase) ...小結
在需求的實現過程中,我們不斷應用正交設計四原則,最終得到了很多小類(也有一些小函式),再透過組合來完成一個個既定需求,不但領域表達力強,而且非常靈活,容易應對變化。
字元的謂詞是本文的重點,有以下三類
分類 | 謂詞 |
---|---|
原子語義 | IsDigit IsUpper Equals IsSpace |
修飾語義 | Not IgnoreCase |
組合語義 | AnyOf AllOf |
它的領域模型如下所示:
char-spec.png
作者:_張曉龍_
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1727/viewspace-2805192/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Specifications 構建動態查詢
- CSS content character ACSS
- python character stringPython
- Hive throws: WstxParsingException: Illegal character entity: expansion character (code 0x8)HiveException
- Swift 字元(Character)講解Swift字元
- 【Lintcode】318. Character Grid
- Unknown initial character set index ‘255‘ received from server. Initial client character set can beIndexServerclient
- Caused by: Error: ' ' is not a valid resource name characterError
- LeetCode之Shortest Distance to a Character(Kotlin)LeetCodeKotlin
- C++筆記 14:審慎使用異常規格(exception specifications)C++筆記Exception
- Would you like to develop a story for your character?dev
- The character that plays in MyCareer will be constantly browsing a false TwitterFalse
- Python 錯誤 SyntaxError: invalid character in identifierPythonErrorIDE
- [原創] How to show chinese character in Git StatusGit
- Java裡的Character類的基本用法Java
- pdf轉word格式PDF to word for MacMac
- JAVA類庫之——Character類(持續更新)Java
- [LeetCode] 2516. Take K of Each Character From Left and RightLeetCode
- Character Animator 2024 for Mac v24.0 啟用版Mac
- pt-online-schema-change 錯誤集 Wide characterIDE
- DataPump Export (EXPDP) Fails With Error LPX-216 Invalid CharacterExportAIError
- Hello Word!
- hello word
- Word Ladder
- word 2021:Word 2021 LTSC for Mac 中文版Mac
- 安裝benchmarksql報java:143: error: unmappable character for encoding ASCIISQLJavaErrorAPPEncodingASCII
- mysql關於字符集character set的總結MySql
- Jsp Unescaped xml character報錯的解決辦法JSXML
- An invalid XML character (Unicode: 0x10) was found in the value of attributeXMLUnicode
- word-break 和 word-wrap 的區別
- word-wrap同word-break的區別
- pdf 轉 word
- word小程式
- Leetcode Word SearchLeetCode
- Java操作WordJava
- linux - word frequencyLinux
- Python操作WordPython
- docxtpl - word模板