最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

設(shè)計模式和函數(shù)式編程——從策略模式開始

2022-01-08 10:15 作者:友紀(jì)V-入OP  | 我要投稿

半年沒有學(xué)習(xí)設(shè)計模式了,這半年以來主要做的框架開發(fā)工作,也算是有一些實踐經(jīng)驗(雖然遠(yuǎn)遠(yuǎn)不夠),同時也是了解了很多函數(shù)式編程的概念,寫的代碼里狀態(tài)越來越少,代碼風(fēng)格越來越聲明式(好久沒寫過原生的for和while了hhh),也開始覺得一些設(shè)計模式變得臃腫起來了?,F(xiàn)在繼續(xù)回來學(xué)習(xí)設(shè)計模式,順便看看它們結(jié)合函數(shù)式編程中的概念會對樣板代碼有如何的簡化。

這里我選擇了策略模式進(jìn)行學(xué)習(xí),之后是命令模式和狀態(tài)模式,選擇其的原因是因為這三個模式經(jīng)由借助Java8所提供的函數(shù)式的工具——函數(shù)式接口和閉包——能很大程度地簡化。

函數(shù)式接口和閉包

函數(shù)式接口結(jié)合Lambda表達(dá)式,使我們能夠書寫作為值/字面量的函數(shù)。這樣,將函數(shù)作為值來看待,作為入?yún)魅?,作為返回值等使用方法都變得明顯和符合直覺了。曾經(jīng)我們只能夠傳遞“名詞”給函數(shù),而現(xiàn)在我們能傳遞“動詞”了。

而閉包使我們能夠捕獲外部作用域的變量,從而構(gòu)造一個“窮人的類”——就如同類是數(shù)據(jù)和行為的一個聚合一樣,閉包函數(shù)也是這樣一個聚合,只不過行為只有一種罷了,比如說下面兩個Counter完全可以認(rèn)為是等價的——

再考慮另一個情形——我們試圖用多線程執(zhí)行某個task,而這個task有一個依賴的對象。如果在上古時代的Java,我們必須創(chuàng)建一個實現(xiàn)Runnable的類來包含相應(yīng)依賴,比如需要這么寫——

這里故意沒有使用匿名實現(xiàn)類,但其實使用匿名實現(xiàn)類的話也需要用到閉包。

需要多加一個類!如果這個類只用一次的話,那也太麻煩了!而在Java 8里,我們可以這么寫——

如果我們有多個task都會利用這個依賴呢?舊時代的Java就只能對每個task都創(chuàng)建一個類了。我們可以用方法引用(本質(zhì)上也是匿名函數(shù))來解決這個問題——建立一個類來包含依賴的對象,把每個task作為一個方法并在方法中使用該依賴,如taskA,taskB。

或者也可以通過函數(shù)參數(shù)來注入依賴,如taskC,使用時使用匿名函數(shù)對該task進(jìn)行調(diào)用,注入依賴。

容易發(fā)現(xiàn),這樣的代碼雖然更加簡短,但是卻是破壞了開閉原則的——各種Task都定義在這個類里,如果要增加新的Task,則必須要修改源代碼。但這在實踐上真的會造成影響嗎?應(yīng)當(dāng)具體問題具體分析。

也需要知道的是,雖然Java 8為函數(shù)式編程做了一些努力,但它的完善程度仍舊是遠(yuǎn)遠(yuǎn)不夠的,它仍舊試圖把函數(shù)都當(dāng)作特定類型的對象來對待,這樣即使兩個函數(shù)的函數(shù)簽名相同,它們兩個也未必是能夠互換使用的。這在異常處理,函數(shù)組合等地方都帶來了很大的麻煩,而Scala或Kotlin在這方面做的很好——首先函數(shù)的簽名就是函數(shù)的類型,這在很多時候甚至比被迫的命名還要清晰——Int -> Int可比IntUnaryOperator要好理解的多了;而且函數(shù)仍被當(dāng)作類來看待,因此也具有自己的方法——和其他的函數(shù)進(jìn)行組合等。這是Java做不到的(Java提供的函數(shù)式接口似乎普遍包含了一個組合方法andThen,Function接口包含了compose,但是并不統(tǒng)一),即使能做到也缺乏泛用性。而試圖進(jìn)行柯里化等操作時,得到的函數(shù)簽名更是不忍直視,比如Int -> Int -> Int -> Int會得到Function<Integer, Function<Integer, Function<Integer, Integer>>>,并且調(diào)用時也得fn.apply(1).apply(2).apply(3),實在難以使用。

策略模式

策略模式就本質(zhì)上說來,就是將為特定目的/接口的算法封裝在同樣接口的類中,使我們能夠方便添加新的算法,以及更換/切換使用的算法。策略模式無論是從面向?qū)ο蟮慕嵌冗€是從函數(shù)式的角度都是非常容易理解的。一個簡單的例子就是,JDBC如何適配多個數(shù)據(jù)庫?答案是提供同樣的接口,為每個數(shù)據(jù)庫都對該接口進(jìn)行實現(xiàn),在運行時選擇使用的數(shù)據(jù)庫的對應(yīng)的實現(xiàn)類。

考慮這樣的一個簡單情景——我們在維護(hù)一個電影院的售票系統(tǒng),現(xiàn)在我們要根據(jù)用戶的年齡和類型來計算票價(假設(shè)票價只和用戶的年齡和類型相關(guān),和電影等其它屬性無關(guān))。根據(jù)票價的規(guī)則,我們快速整出來一個原型——

We did it!代碼很簡單,可是它真能“不忘初心”嗎?non non噠喲,基礎(chǔ)價變了怎么辦?成年人的定義變了怎么辦?遇上節(jié)日了不打折嗎?會員的折扣變了怎么辦?顯然,這代碼可抵抗不了業(yè)務(wù)的變化,它必須得動。

介紹和示例

能夠發(fā)現(xiàn),當(dāng)我們進(jìn)行對計算規(guī)則進(jìn)行改變的時候,我們實際上只需要改變getPrice方法中的內(nèi)容,而buyTicket方法,以及getPrice的簽名(接口)都是不變的。答案很顯然了,我們把getPrice抽象成接口,而讓具體的算法去實現(xiàn)這樣的接口就行了,而buyTicket則利用該接口對具體算法進(jìn)行使用。這樣的接口就稱為策略(Strategy)。

這樣,當(dāng)業(yè)務(wù)有調(diào)整的時候,只需要編寫新的策略,并通過配置文件等形式修改注入的具體策略即可,甚至能夠在運行時對使用的策略進(jìn)行修改。我們還可以讓策略之間互相依賴,比如對另外一個策略的票價再打個折之類的。

總而言之(真快?。。呗阅J街谐霈F(xiàn)三種角色——上下文(context),使用策略的地方,即客戶端;抽象策略,或者說策略的接口,其將被上下文所使用;具體策略,顧名思義。

FP的角度

從函數(shù)式編程的角度來看,這一個個的具體的策略類實際上是一個個具有同樣的接口/簽名的函數(shù),它們被命名,被保存了。于是,我們可以通過函數(shù)來表示具體策略,讓函數(shù)的簽名來代替抽象策略,從而消滅抽象策略這一角色,在上下文中直接使用符合該函數(shù)簽名的函數(shù)作為具體策略。比如,這里的getPrice函數(shù),它的簽名為(int, int) -> int,或者從Java的話說,BiFunction<Integer, Integer, Integer>Function<Tuple2<Integer, Integer>, Integer>,抽象策略的類就可直接使用該函數(shù)類來代替,而具體策略只需要實現(xiàn)該接口的示例。比如我們可以這么寫——

適用場景

對同一個接口,需要能夠切換多個實現(xiàn)的情況下使用策略模式非常適合,比如前面說到的票價計算場景,以及選擇使用介質(zhì)的緩存(本地,內(nèi)存,磁盤,網(wǎng)絡(luò)),統(tǒng)一不同的文件系統(tǒng)等。需注意的是如果策略包含多個方法,則還是使用面向?qū)ο蟮氖侄胃奖阈?/p>

go further

我們可以通過函數(shù)組合的方法對不同策略進(jìn)行組合,達(dá)到復(fù)用代碼,或者對結(jié)果進(jìn)行“代理”的目的。比如我們可以讓策略的返回值來乘以一個數(shù)來模擬打折情況——

我們也可以通過流式接口等形式來創(chuàng)建策略的工廠類,通過DSL來描述業(yè)務(wù),最終創(chuàng)建出最后的服務(wù),比如可能可以這樣——

甚至利用動態(tài)語言等的特性,嵌入個lua虛擬機整整活?這里還會有無數(shù)的騷操作,但是我想不出來了,告辭!

關(guān)于函數(shù)組合

什么是函數(shù)組合?我們知道,數(shù)學(xué)上的函數(shù)是兩個集合之間的映射,如f(x) = x + 1 (x ∈ R)為一個實數(shù)集到實數(shù)集的映射。而類型可以認(rèn)為是一個數(shù)據(jù)的集合,如int型代表……-1,0,1,……的集合,char型代表'a','b',……的集合。計算機中的函數(shù)因此也可表示從集合到集合的映射,如上面的getPrice函數(shù),其可以表達(dá)為(int, int) -> int,即一個(int, int)——int型和int型的笛卡爾積——的集合到int型的映射。

如果我們有一個函數(shù)f : A -> B,其中A為輸入值的類型,B為輸出值的類型,又有一個函數(shù)g : B -> C,這樣我們就可以組合這兩個函數(shù),得到函數(shù)g . f : A -> C。其中(g . f)(x) = g(f(x))

扯這些有啥用呢?我們可以通過函數(shù)簽名來對函數(shù)進(jìn)行組合,從而把簡單的函數(shù)組裝成復(fù)雜的函數(shù),以更聲明式的手段表達(dá)業(yè)務(wù)邏輯。比如在Java中,我們可以寫Stream.of(1, 2, 3).filter(i -> i % 2 == 1).map(i -> i * 3).reduce(0, Integer::sum),而在函數(shù)式語言中,我們會寫sum . map (* 3) . filter (\i -> div i 2 == 1)。一個js的例子如下——

在Java中,我們對數(shù)據(jù)進(jìn)行鏈?zhǔn)降奶幚恚诤瘮?shù)式編程中,我們通過組合小的,易理解、處理、證明的函數(shù)來構(gòu)造最終的復(fù)雜函數(shù)并將其應(yīng)用到數(shù)據(jù)上。顯然后者是形式更加清晰(至少在Haskell里是這樣的……),測試更為容易,更加容易進(jìn)行復(fù)用的。



設(shè)計模式和函數(shù)式編程——從策略模式開始的評論 (共 條)

分享到微博請遵守國家法律
海淀区| 准格尔旗| 和顺县| 南城县| 义马市| 钦州市| 湘乡市| 丰镇市| 阿尔山市| 称多县| 牙克石市| 新宁县| 龙井市| 石柱| 香河县| 盐津县| 永川市| 宁化县| 花莲县| 祁连县| 托克逊县| 曲靖市| 平和县| 肃南| 新源县| 北京市| 乌审旗| 驻马店市| 灵寿县| 焦作市| 图片| 十堰市| 延川县| 中西区| 烟台市| 吴堡县| 怀仁县| 沙田区| 正宁县| 贵溪市| 荣昌县|