Character Specifications for A Word in Golang

序言

筆者有幸參加了一次Code Retreat活動(dòng),整個(gè)過(guò)程很有收獲庐冯,本文通過(guò)Golang語(yǔ)言來(lái)回放一下孽亲。

需求一:判斷某個(gè)單詞是否包含數(shù)字

這個(gè)需求比較簡(jiǎn)單,代碼實(shí)現(xiàn)如下:

func HasDigit(word string) bool {
    for _, c := range word {
        if unicode.IsDigit(c) {
            return true
        }
    }
    return false
}

需求二:判斷某個(gè)單詞是否包含大寫(xiě)字母

有了需求一的基礎(chǔ)后展父,可以通過(guò)copy-paste快速實(shí)現(xiàn)需求二:

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函數(shù)和HasUpper函數(shù)除過(guò)if的條件判斷外,其余代碼都一樣栖茉,所以我們使用抽象這個(gè)強(qiáng)大的屠龍刀來(lái)消除重復(fù):

  1. 定義一個(gè)接口CharSpec篮绿,作為所有字符謂詞的抽象,方法Satisfy用來(lái)判斷謂詞是否為真
  2. 針對(duì)需求一定義具有原子語(yǔ)義的謂詞IsDigit
  3. 針對(duì)需求二定義具有原子語(yǔ)義的謂詞IsUpper

謂詞相關(guān)代碼實(shí)現(xiàn)如下:

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語(yǔ)義函數(shù)亲配,而Exists具有Has語(yǔ)義,同時(shí)表達(dá)力很強(qiáng):

func Exists(word string, spec CharSpec) bool {
    for _, c := range word {
        if spec.Satisfy(c) {
            return true
        }
    }
    return false
}

通過(guò)Exists判斷某個(gè)單詞word是否包含數(shù)字:

isDigit := IsDigit{}
ok := Exists(word, isDigit)
...

通過(guò)Exists判斷某個(gè)單詞word是否包含大寫(xiě)字母:

isUpper := IsUpper{}
ok := Exists(word, isUpper)
...

其實(shí)需求二的故事還沒(méi)講完:)

對(duì)于普通的程序員來(lái)說(shuō)惶凝,能完成上面的代碼已經(jīng)很好了吼虎,而對(duì)于經(jīng)驗(yàn)豐富的程序員來(lái)說(shuō),在需求一剛完成后可能就發(fā)現(xiàn)了新的變化方向苍鲜,即單詞的Has語(yǔ)義和字符的Is語(yǔ)義是兩個(gè)不同的變化方向思灰,所以在需求二開(kāi)始前就通過(guò)重構(gòu)分離了變化方向:

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
}

通過(guò)Exists判斷某個(gè)單詞word是否包含數(shù)字:

isDigit := IsDigit{}
ok := Exists(word, isDigit)
...

在需求二出來(lái)后,謂詞被第一顆子彈擊中混滔,我們根據(jù)Uncle Bob的建議洒疚,應(yīng)用開(kāi)放封閉原則,于是也就寫(xiě)出了普通程序員在需求二中消除重復(fù)后的代碼坯屿。

殊途同歸油湖,這并不是巧合,而是有理論依據(jù)领跛。

我們一起回顧一下 袁英杰先生 提出的正交設(shè)計(jì)四原則

  1. 一個(gè)變化導(dǎo)致多處修改:消除重復(fù)
  2. 多個(gè)變化導(dǎo)致一處修改:分離不同的變化方向
  3. 不依賴(lài)不必要的依賴(lài):縮小依賴(lài)范圍
  4. 不依賴(lài)不穩(wěn)定的依賴(lài):向著穩(wěn)定的方向依賴(lài)

這四個(gè)原則的提出是針對(duì)簡(jiǎn)單設(shè)計(jì)四原則中的第二條“消除重復(fù)”乏德,使得目標(biāo)的達(dá)成有章可循。我們應(yīng)用正交設(shè)計(jì)四原則吠昭,可以將系統(tǒng)分解成很多單一職責(zé)的小類(lèi)(也有一些小函數(shù))喊括,然后再將它們根據(jù)需要而靈活的組合起來(lái)。

細(xì)細(xì)品味正交設(shè)計(jì)四原則怎诫,你就會(huì)發(fā)現(xiàn):第一條是被動(dòng)策略,而后三條是主動(dòng)策略贷痪。這就是說(shuō)幻妓,第一條是一種事后補(bǔ)救的策略,而后三條是一種事前預(yù)防的策略,目標(biāo)都是為了消除重復(fù)肉津。

從上面的分析可以看出强胰,普通的程序員習(xí)慣使用被動(dòng)策略,而經(jīng)驗(yàn)豐富的程序員更喜歡使用主動(dòng)策略妹沙。Anyway偶洋,他們殊途同歸,都消除了重復(fù)距糖。

需求三:判斷某個(gè)單詞是否包含_

不管是包含下劃線(xiàn)還是中劃線(xiàn)玄窝,都有原子語(yǔ)義Equals,我們將代碼快速實(shí)現(xiàn):

type Equals struct {
    c rune
}

func (e Equals) Satisfy(c rune) bool {
    return c == e.c
}

通過(guò)Exists判斷某個(gè)單詞word是否包含_:

isUnderline := Equals{'_'}
ok := Exists(word, isUnderline)
...

需求四:判斷某個(gè)單詞是否不包含_

字母是下劃線(xiàn)的謂詞是Equals悍引,那么字母不是下劃線(xiàn)的謂詞就是給Equals前增加一個(gè)修飾語(yǔ)義Not恩脂,Not修飾謂詞后是一個(gè)新的謂詞,代碼實(shí)現(xiàn)如下:

type Not struct {
    spec CharSpec
}

fun (n Not) Satisfy(c rune) bool {
    return !n.spec.Satisfy(c)
}

單詞不包含下劃線(xiàn)趣斤,就不是Exists語(yǔ)義了俩块,而是ForAll語(yǔ)義,代碼實(shí)現(xiàn)如下:

func ForAll(word string, spec CharSpec) bool {
    for _, c := range str {
        if !spec.Satisfy(c) {
            return false
        }
    }
    return true
}

通過(guò)ForAll判斷某個(gè)單詞word是否不包含_:

isNotUnderline := Not{Equals{'_'}}
ok := ForAll(word, isNotUnderline)
...

功能雖然實(shí)現(xiàn)了浓领,但是我們發(fā)現(xiàn)Exists函數(shù)和ForAll函數(shù)有很多代碼是重復(fù)的玉凯,使用重構(gòu)基本手法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)
}

需求五:判斷某個(gè)單詞是否包含_或者*

字母是x或y的謂詞具有組合語(yǔ)義AnyOf,其中x為Equals{'_'}联贩,y為Equals{'*'}漫仆,代碼實(shí)現(xiàn)如下:

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
}

通過(guò)Exists判斷某個(gè)單詞word是否包含_或*:

isUnderlineOrStar := AnyOf{[]CharSpec{Equals{'_'}, Equals{'*'}}}
ok := Exists(word, isUnderlineOrStar)
...

需求六:判斷某個(gè)單詞是否包含空白符,但除去空格

空白符包括空格撑蒜、制表符和換行符等歹啼,具體見(jiàn)下面代碼:

func IsSpace(r rune) bool {
    // This property isn't the same as Z; special-case it.
    if uint32(r) <= MaxLatin1 {
        switch r {
        case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0:
            return true
        }
        return false
    }
    return isExcludingLatin(White_Space, r)
}

字母是空白符的謂詞還沒(méi)有實(shí)現(xiàn),我們定義具有原子語(yǔ)義的謂詞IsSpace:

type IsSpace struct {

}

func (i IsSpace) Satisfy(c rune) bool {
    return unicode.IsSpace(c)
}

字母是x和y的謂詞具有組合語(yǔ)義AllOf座菠,其中x為IsSpace狸眼,y為Not{Equals{' '}},代碼實(shí)現(xiàn)如下:

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
}

通過(guò)Exists判斷某個(gè)單詞word是否包含空白符浴滴,但除去空格:

isSpaceAndNotBlank := AllOf{[]CharSpec{IsSpace{}, Not{Equals{' '}}}}
ok := Exists(word, isSpaceAndNotBlank)
...

需求七:判斷某個(gè)單詞是否包含字母x拓萌,且不區(qū)分大小寫(xiě)

不區(qū)分大小寫(xiě)是一種具有修飾語(yǔ)義的謂詞IgnoreCase,代碼實(shí)現(xiàn)如下:

type IgnoreCase struct {
    spec CharSpec
}

func (i IgnoreCase) Satisfy(c rune) bool {
    return i.spec.Satisfy(unicode.ToLower(c))
}

通過(guò)Exists判斷某個(gè)單詞word是否包含字母x升略,且不區(qū)分大小寫(xiě):

isXIgnoreCase := IgnoreCase{Equals{'x'}}
ok := Exists(word, isXIgnoreCase)
...

小結(jié)

在需求的實(shí)現(xiàn)過(guò)程中微王,我們不斷應(yīng)用正交設(shè)計(jì)四原則,最終得到了很多小類(lèi)(也有一些小函數(shù))品嚣,再通過(guò)組合來(lái)完成一個(gè)個(gè)既定需求炕倘,不但領(lǐng)域表達(dá)力強(qiáng),而且非常靈活翰撑,容易應(yīng)對(duì)變化罩旋。

字符的謂詞是本文的重點(diǎn),有以下三類(lèi)

分類(lèi) 謂詞
原子語(yǔ)義 IsDigit
IsUpper
Equals
IsSpace
修飾語(yǔ)義 Not
IgnoreCase
組合語(yǔ)義 AnyOf
AllOf

它的領(lǐng)域模型如下所示:

char-spec.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涨醋,隨后出現(xiàn)的幾起案子瓜饥,更是在濱河造成了極大的恐慌,老刑警劉巖浴骂,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乓土,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡溯警,警方通過(guò)查閱死者的電腦和手機(jī)趣苏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)愧膀,“玉大人拦键,你說(shuō)我怎么就攤上這事¢萘埽” “怎么了芬为?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蟀悦。 經(jīng)常有香客問(wèn)我媚朦,道長(zhǎng),這世上最難降的妖魔是什么日戈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任询张,我火速辦了婚禮,結(jié)果婚禮上浙炼,老公的妹妹穿的比我還像新娘份氧。我一直安慰自己,他們只是感情好弯屈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布蜗帜。 她就那樣靜靜地躺著,像睡著了一般资厉。 火紅的嫁衣襯著肌膚如雪厅缺。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天宴偿,我揣著相機(jī)與錄音湘捎,去河邊找鬼。 笑死窄刘,一個(gè)胖子當(dāng)著我的面吹牛窥妇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娩践,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼活翩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逞带!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纱新,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎穆趴,沒(méi)想到半個(gè)月后脸爱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡未妹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年簿废,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片络它。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡族檬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出化戳,到底是詐尸還是另有隱情单料,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布点楼,位于F島的核電站扫尖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掠廓。R本人自食惡果不足惜换怖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蟀瞧。 院中可真熱鬧沉颂,春花似錦、人聲如沸悦污。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)塞关。三九已至抬探,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帆赢,已是汗流浹背小压。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椰于,地道東北人怠益。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瘾婿,于是被迫代替她去往敵國(guó)和親蜻牢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烤咧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • //Clojure入門(mén)教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語(yǔ)閱讀 3,682評(píng)論 0 7
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法抢呆,內(nèi)部類(lèi)的語(yǔ)法煮嫌,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法抱虐,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,664評(píng)論 18 399
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,304評(píng)論 25 707
  • 從地鐵站出來(lái)昌阿,外面的風(fēng)還有點(diǎn)冷。拿著手中的地圖開(kāi)始了這段旅途恳邀。 在路上的開(kāi)始懦冰,似乎沒(méi)有那么順利。市區(qū)的路線(xiàn)很復(fù)雜谣沸,...
    東東方閱讀 798評(píng)論 0 4
  • 在廣州吃吃喝喝的旅行即將結(jié)束刷钢。按原計(jì)劃,今天要來(lái)珠海乳附,準(zhǔn)備明天去澳門(mén)内地。所以早上就沒(méi)作其他安排,在酒店休息赋除。 中午退...
    茗心M閱讀 243評(píng)論 0 2