Why
在最近的設(shè)計(jì)過(guò)程中,我發(fā)現(xiàn)我不止一次的使用一種類(lèi)似的設(shè)計(jì)方案,這種設(shè)計(jì)方案會(huì)從紛繁復(fù)雜的表象中發(fā)現(xiàn)(或者說(shuō)理清)業(yè)務(wù)的本質(zhì),使代碼變得簡(jiǎn)單,系統(tǒng)更容易維護(hù)。第一次運(yùn)用這種技巧已經(jīng)是幾年前的事情了,因?yàn)樽罱謨纱斡玫竭@個(gè)技巧,因此決定把這種技巧寫(xiě)下來(lái),并給它取了一個(gè)名字: Pattern Mapping, 中文名叫 模式映射 。
先來(lái)看一個(gè)例子,我們有一個(gè)系統(tǒng),它的其中一個(gè)業(yè)務(wù)是把數(shù)據(jù)從一種 源數(shù)據(jù)庫(kù)(Source) 同步到另外一種 目標(biāo)數(shù)據(jù)庫(kù)(Target) , 而不同數(shù)據(jù)庫(kù)之間進(jìn)行同步的時(shí)候,為了達(dá)到最高的性能,會(huì)采用不同的同步方式,比如(這里只是為了說(shuō)明問(wèn)題,不代表真實(shí)實(shí)現(xiàn)):
- MySQL -> Spark: 采用MR
- MySQL -> Oracle: 采用DataX
- MySQL -> HBase: 采用HBaseBulkLoad

一種比較原始的做法是針對(duì)每種不同的Source -> Target組合,我們都各自編碼,寫(xiě)著寫(xiě)著,你會(huì)發(fā)現(xiàn)有些組合的業(yè)務(wù)邏輯是類(lèi)似的,比如MySQL -> Oracle, MySQL -> PostgreSQL都是用DataX同步,于是你的代碼里面會(huì)出現(xiàn)一些if-else判斷邏輯:
if ((source == MySQL && target == Oracle)
|| (source == MySQL && target == PostgreSql)) {
// balabalabala
}
...
if ((source == aaa && target == bbb)
|| (source == ccc && target == ddd)) {
// balabalabala
}
...
if ((source == eee && target == fff)
|| (source == ggg && target == hhh)) {
// balabalabala
}
...
在系統(tǒng)開(kāi)發(fā)的初期,這種if-else邏輯不多的時(shí)候,還沒(méi)有什么問(wèn)題。但是當(dāng)我們支持的數(shù)據(jù)源越來(lái)越多時(shí)候,你會(huì)發(fā)現(xiàn)這種if-else如此之多,以至于你已經(jīng)沒(méi)辦法理清所有的業(yè)務(wù)邏輯;你要新增對(duì)于一種數(shù)據(jù)庫(kù)的支持的時(shí)候,你不知道到底哪些if-else需要進(jìn)行修改。因?yàn)檫@些Source和Target之間產(chǎn)生了類(lèi)似笛卡爾積的關(guān)系,凡人是理不清這之前的關(guān)系的:

代碼開(kāi)始腐爛,人員開(kāi)始流失,可能要考慮推倒重來(lái)了。
Pattern Mapping
等等,讓Pattern Mapping來(lái)試試拯救你。
Pattern Mapping,或者說(shuō)模式映射,是指針對(duì)一組紛繁復(fù)雜的業(yè)務(wù),我們不是直接針對(duì)裸的原始輸入進(jìn)行業(yè)務(wù)規(guī)則的定義,而是從中抽象出其本質(zhì)的幾種Pattern, 然后把原始輸入跟Pattern進(jìn)行Mapping,從而構(gòu)造出整個(gè)系統(tǒng)。
這些Pattern其實(shí)才是業(yè)務(wù)的本質(zhì),因此這樣理出的Pattern的個(gè)數(shù)一般會(huì)比原始數(shù)據(jù)源的個(gè)數(shù)小一個(gè)數(shù)量級(jí),從而降低了整個(gè)問(wèn)題的復(fù)雜度。比如說(shuō)上面的例子,我們其實(shí)可以抽象出幾種Pattern:
| Pattern | Mapping |
|---|---|
| 可以用DataX同步的 | MySQL -> Oracle, MySQL -> Postgresql |
| 可以用BulkLoad同步的 | 任意數(shù)據(jù)庫(kù) -> HBase |
| 可以用MR同步的 | Spark -> MySQL |
這樣代碼就變成了:
if (isDataXCompatiable(source, target)) {
// 使用DataX同步
}
...
if (isBulkLoadCompatible(source, target)) {
// 使用BulkLoad進(jìn)行同步
}
...
if (isMRCompatible(source, target)) {
// 使用MR進(jìn)行同步
}
你會(huì)發(fā)現(xiàn)這個(gè)代碼里面沒(méi)有任何地方對(duì)具體的 source, target 的類(lèi)型進(jìn)行判斷,我們編程針對(duì)的是業(yè)務(wù)的本質(zhì),而不是裸的業(yè)務(wù)輸入,從而減少了這種if-else的個(gè)數(shù),在原來(lái)的笛卡爾積問(wèn)題中間加入了中間的Pattern, 降低了問(wèn)題的復(fù)雜度:

它把原來(lái)的
M x N的問(wèn)題 變成了M + X + N的問(wèn)題。
結(jié)論
Pattern Mapping適用的場(chǎng)景是: 業(yè)務(wù)邏輯的輸入因子有很多可能的值,這些因子之間可能會(huì)相互作用,但是不同因子之間是有一些共通的Pattern。使用Pattern Mapping使得我們可以抓住業(yè)務(wù)的本質(zhì),使得我們面向業(yè)務(wù)的本質(zhì)編程(而不是裸的業(yè)務(wù)輸入), 降低了業(yè)務(wù)的復(fù)雜性,提高了代碼的可維護(hù)性。