函數(shù)式編程思想簡介

在平時(shí)的iOS開發(fā)中,我們大部分還是依靠著最早的MVC的思想開發(fā)著咳燕,最多也是進(jìn)行了部分變種季蚂,比如MVP茫船,MVVM琅束,這些都是完全的面向?qū)ο蟮乃枷耄谔幚硪恍┖唵蔚膱鼍暗臅r(shí)候還是非常有效的算谈,但是在某些場合狰闪,我們總會(huì)覺得難以解耦,難以進(jìn)行模塊化拆分濒生,這是由于目前大部分架構(gòu)中,在業(yè)務(wù)邏輯部分依然是過程性的幔欧,整個(gè)過程中還是需要依賴大量的其他模塊罪治。

這次我們來看看函數(shù)式編程的思想,以另一種角度來看問題是否能夠去解決部分問題礁蔗。

概念

函數(shù)式編程對于我們這種熟悉了面向?qū)ο蟮娜藖碚f觉义,是非常別扭和難以理解的。首先我們需要了解幾個(gè)概念浴井,這里不討論嚴(yán)格的概念晒骇,可以說這是我結(jié)合iOS開發(fā)的部分特性所做的一些理解。

函數(shù)是一等公民

這是函數(shù)式編程中最重要的思想基礎(chǔ)磺浙。萬物都是對象洪囤,包括函數(shù)。這個(gè)思想的引入撕氧,讓我們可以將函數(shù)作為參數(shù)瘤缩、返回值這一類原本對象才可以做的事情。在objc中的block伦泥,swift中的closure都可以認(rèn)為是函數(shù)式編程的基礎(chǔ)剥啤。

高階函數(shù)

參數(shù)或者返回值中存在函數(shù)對象的,都認(rèn)為是高階函數(shù)不脯「樱可以說高階函數(shù)是函數(shù)式編程中的連接器,他同樣也可以實(shí)現(xiàn)比如函數(shù)修飾器這樣的功能防楷。

純函數(shù)

沒有副作用的函數(shù)牺丙,簡單的說就是給以相同的輸入,必定有相同的輸出复局。這也是函數(shù)式編程思想中非常重要的一個(gè)點(diǎn)赘被,因?yàn)闆]有副作用可以讓我們的設(shè)計(jì)變得更加簡單。

monad

關(guān)于monad這個(gè)概念肖揣,很多人都有不同的理解民假,但基本上都是把純函數(shù)連接起來的一種設(shè)計(jì)模式,同時(shí)這也是純的龙优。這個(gè)概念在業(yè)務(wù)開發(fā)中基本上不太可能實(shí)現(xiàn)羊异,但是在業(yè)務(wù)的部分場景還是可以做到的事秀。

優(yōu)勢

由于函數(shù)式編程中要求大量的純函數(shù),那么我們就可以很容易的進(jìn)行單元測試野舶。

同時(shí)在多線程編程中易迹,由于其獨(dú)立性和無副作用,我們也不需要思考數(shù)據(jù)競爭平道,死鎖等這類問題睹欲。也能更靈活的進(jìn)行功能拆分和執(zhí)行分配。

函數(shù)式編程是一套比較全面的編程思想一屋,對于我們面向?qū)ο蟊容^熟悉的人來說窘疮,有些地方確實(shí)會(huì)比較奇怪。雖然說我們也可以用面向?qū)ο髞韺?shí)現(xiàn)類似的功能冀墨,但是函數(shù)式編程會(huì)更加簡單和直接闸衫。

框架

函數(shù)式編程是專門為了計(jì)算這類場景而生的,目前由于腳本語言的興盛诽嘉,這類思想也同時(shí)融入了很多面向?qū)ο笳Z言中蔚出。

promise

promise可以說是我最早接觸到的函數(shù)式編程相關(guān)框架。promise的初衷是為了解決異步callback的多次調(diào)用而開發(fā)的虫腋。

step1(function (err) {
    if (err) print(err)
    else step2(function (err) {
        if (err) print(err)
        else step3(function (err) {
            // go on ...
        })
    })
})

那么通過promise骄酗,我們可以改寫為:

step1()
.then(step2)
.then(step3)
.then(/* go on ... */)
.catch(print)

ReactiveX

同樣類似的ReactiveX也具有相同的效果,不過RX把注意點(diǎn)更加放在了對數(shù)據(jù)的處理上面悦冀,這也就正式的把我們從邏輯的思維中脫離開來酥筝,從而關(guān)注數(shù)據(jù)的流動(dòng)上。后面會(huì)來討論這種思維的變化給我們帶來了什么雏门。

RX中signal, observer, observable, subject主要負(fù)責(zé)數(shù)據(jù)的流動(dòng)嘿歌,scheduler負(fù)責(zé)執(zhí)行線程相關(guān)操作。

Redux

參考ReSwift

redux.png

可以說這是我接觸到的第一個(gè)以函數(shù)式編程為基礎(chǔ)的茁影,專注于數(shù)據(jù)流動(dòng)的框架宙帝。

關(guān)于這個(gè)框架的詳細(xì)介紹,我相信有很多人都比我更加了解募闲,也比我講的好步脓,所以這里就不做介紹了。

數(shù)據(jù)流動(dòng)

一個(gè)請求的例子

在我們平時(shí)的開發(fā)過程中浩螺,基本上關(guān)注的是業(yè)務(wù)的流程實(shí)現(xiàn)靴患,按照邏輯的順序進(jìn)行思考和設(shè)計(jì),如果我們按照標(biāo)準(zhǔn)的面向?qū)ο髞碓O(shè)計(jì)一個(gè)請求要出,那么很有可能是這樣的鸳君。

- (void)request {
  Request *req = [Request new]
  req.delegate = self;
  [req start];
}

- (void)requestComplete:(Request *)req {
  Request *req = [Request new];
  [req startWithComplete:^(Request *req) {
    if (req.error == nil) {
      NSDictionary *json = [JSON parse:req.data];
      ClassA *a = [ClassA from:json];
      // ...
    }
    else {

    }
  }]
}

這是一個(gè)非常典型的設(shè)計(jì)方式,當(dāng)我們只有一個(gè)請求的時(shí)候這樣做也非常簡單患蹂,但是一旦擁有多個(gè)請求的時(shí)候或颊,事情就變得很復(fù)雜砸紊。那么如果我們使用函數(shù)式來改寫,就比如AF所做的那樣囱挑。

- (void)request {
  Request *req = [Request new];
  [req startWithComplete:^(Request *req) {
    if (req.error == nil) {
      NSDictionary *json = [JSON parse:req.data];
      ClassA *a = [ClassA from:json];
      // ...
    }
    else {

    }
  }]
}

這樣在出現(xiàn)多個(gè)請求的時(shí)候醉顽,回調(diào)也能保證其唯一性。但是這個(gè)依然還是有一些問題平挑。

  1. 數(shù)據(jù)解析和model化等操作依然需要在回調(diào)時(shí)自己去調(diào)用游添,如果將這些放入Request中,又難以保持低耦合性通熄。
  2. 自動(dòng)化測試?yán)щy唆涝,因?yàn)檎埱蠓祷睾蟊厝皇墙又晥D的更新或者業(yè)務(wù)邏輯的執(zhí)行,自動(dòng)化測試一旦涉及這些會(huì)顯得很臃腫棠隐。
  3. 當(dāng)多個(gè)請求在需要并發(fā),而回調(diào)需要保持同步的時(shí)候檐嚣。
  4. 流量控制只能依賴于網(wǎng)絡(luò)層的來控制助泽,同時(shí)線程控制依然需要手動(dòng)控制,可能GCD會(huì)讓這類操作稍微簡單一點(diǎn)嚎京。

那么如果我們只關(guān)注數(shù)據(jù)來改寫呢嗡贺,可能會(huì)是這樣的,為了更適合函數(shù)式編程鞍帝,這里采用js來改寫:

Request().then(data => {
  let json = JOSN.parse(data)
  let a = ClassA.from(json)
  // ...
}).catch(err => {

})

那么我們來看看如何解決上述的幾個(gè)問題诫睬。

為了保證低耦合性,我們不希望將解析等操作放入網(wǎng)絡(luò)層中帕涌,但也不希望在業(yè)務(wù)層過多的出現(xiàn)這類代碼摄凡。

Request().then(JSON.parse).then(ClassA.from).then(a => {

}).catch(err => {

})

而其中分離出來的中間過程,由于是純函數(shù)蚓曼,是可以完全的單元測試的亲澡,這也就增強(qiáng)了單元測試的粒度。如果你說客戶端業(yè)務(wù)代碼的單元測試是否有這個(gè)必要纫版,那么我覺得很多時(shí)候沒有這個(gè)時(shí)間和必要床绪,但是當(dāng)我們做一些基礎(chǔ)庫,或者SDK時(shí)其弊,涉及的面非常廣時(shí)癞己,我們不得不去考慮單元測試的必要性,而且覆蓋率越高越好梭伐。

如果要實(shí)現(xiàn)并發(fā)這種呢痹雅?按照數(shù)據(jù)的角度去思考其實(shí)就非常的簡單。

let req1 = Request().then(JSON.parse).then(ClassA.from)
let req2 = Reqeust().then(JSON.parse).then(ClassB.from)
Promise.all([req1, req2]).then(ab => {
  // reload data
}).catch(err => {

})

由于中間過程是純函數(shù)糊识,那么我們要將他們放入子線程也就非常的簡單练慕,甚至RX自己就提供了這樣的接口惰匙。

函數(shù)修飾器

這是某些語言的特性,所以拿出來簡單說說铃将。

一個(gè)函數(shù)修飾器的參數(shù)中必然至少有一個(gè)是函數(shù)项鬼,返回的也依然是函數(shù)。這個(gè)比較難以理解劲阎,我們來舉一個(gè)例子:

function needLogin(f) {
  if (login)
    return f
  else 
    return function () { print('error') }
}

那么我們需要為某個(gè)函數(shù)增加這個(gè)檢查的時(shí)候绘盟,只需要增加一層修飾即可:

let NewRequest = needLogin(Request)
NewRequest().then(...)

python:

@needLogin
def Request()
  # ...
  pass

這種方法的優(yōu)勢在于,我們不需要在請求里去增加這種業(yè)務(wù)代碼悯仙,同時(shí)也不需要在業(yè)務(wù)層里進(jìn)行復(fù)雜的校驗(yàn)龄毡,同時(shí)這樣表示也會(huì)更加接近自然語言。

最后

這里簡單介紹了函數(shù)式編程思想锡垄,和我們平時(shí)開發(fā)所使用的方法都不一致沦零,也沒有哪一種方法更好的定論。但是這一種以數(shù)據(jù)作為主體的方法卻是非常值得我們思考的货岭,因?yàn)榭梢哉J(rèn)為數(shù)據(jù)是不會(huì)存在高耦合這樣的問題路操,這也給我們提供了一種新的解決問題的思路。

這是一種新的思考方式千贯,可以說這里的一些內(nèi)容是無法讓你真正改變的屯仗,很多還是需要靠自身的理解和感悟,才能將其應(yīng)用于平時(shí)的開發(fā)之中搔谴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魁袜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敦第,更是在濱河造成了極大的恐慌峰弹,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芜果,死亡現(xiàn)場離奇詭異垮卓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)师幕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門粟按,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人霹粥,你說我怎么就攤上這事灭将。” “怎么了后控?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵庙曙,是天一觀的道長。 經(jīng)常有香客問我浩淘,道長捌朴,這世上最難降的妖魔是什么吴攒? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮砂蔽,結(jié)果婚禮上洼怔,老公的妹妹穿的比我還像新娘。我一直安慰自己左驾,他們只是感情好镣隶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诡右,像睡著了一般安岂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帆吻,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天域那,我揣著相機(jī)與錄音,去河邊找鬼猜煮。 笑死次员,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的友瘤。 我是一名探鬼主播翠肘,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼檐束,長吁一口氣:“原來是場噩夢啊……” “哼辫秧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起被丧,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盟戏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后甥桂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柿究,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年黄选,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝇摸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡办陷,死狀恐怖貌夕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情民镜,我是刑警寧澤啡专,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站制圈,受9級特大地震影響们童,放射性物質(zhì)發(fā)生泄漏畔况。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一慧库、第九天 我趴在偏房一處隱蔽的房頂上張望跷跪。 院中可真熱鬧,春花似錦完沪、人聲如沸域庇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽听皿。三九已至,卻和暖如春宽档,著一層夾襖步出監(jiān)牢的瞬間尉姨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工吗冤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留又厉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓椎瘟,卻偏偏與公主長得像覆致,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子肺蔚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

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

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,938評論 2 89
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,105評論 1 32
  • 一. Tracy主持人 1.主持人Tracy的三個(gè)標(biāo)簽:(1)14歲男孩媽媽(2)運(yùn)動(dòng)愛好者(游泳)(3)親子學(xué)校...
    娜式Fantacy閱讀 296評論 0 0
  • 感恩的心煌妈,今天有些無能為力幫到朋友,讓我很不舒服宣羊,希望以后自己各個(gè)方面都強(qiáng)大起來璧诵,在朋友需要幫助的時(shí)候,好不費(fèi)力的...
    德勝閱讀 97評論 0 0
  • 希望自己的文字能夠充滿力量仇冯。 已經(jīng)很久沒有好好地鍛煉過寫作能力之宿。 高三為了備考只有好好地給自己洗腦,寫一些淺顯的東...
    寒川朔閱讀 148評論 0 0