由一個(gè)例子理解指令式編程與聲明式編程

SwiftUI 是一個(gè)聲明式的 UI 開發(fā)方式淘这。在能夠進(jìn)一步之前穴张,我們最好先弄清指令式和聲明式兩種編程方式的區(qū)別掉伏,從而更好地理解SwiftUI的強(qiáng)大與精妙缝呕。

指令式編程

C,C++ 和大部分的更早期的語(yǔ)言都遵從指令式編程的范式岖免。一般來(lái)說(shuō)岳颇,指令式編程支持三種語(yǔ)句:

  • 運(yùn)算語(yǔ)句:將某個(gè)值保存到變量中,計(jì)算某個(gè)表達(dá)式的結(jié)果颅湘,或者方法調(diào)用话侧。比如 let a = 1 + 2 就是一個(gè)標(biāo)準(zhǔn)的運(yùn)算語(yǔ)句。

  • 循環(huán)語(yǔ)句:在特定條件下反復(fù)運(yùn)行某些語(yǔ)句闯参,比如 for 和 while瞻鹏。

  • 條件語(yǔ)句:如果某些條件成立時(shí)才運(yùn)行某個(gè)區(qū)塊的代碼悲立,否則就將省去。比如 if 和 switch 都是條件語(yǔ)句新博⌒较Γ”

通過(guò)組合這些語(yǔ)句,我們?cè)谥噶钍骄幊讨兄饤l指示計(jì)算機(jī)如何工作赫悄。早期的指令式編程語(yǔ)言都是針對(duì)計(jì)算機(jī)本身的機(jī)器或匯編語(yǔ)言原献。在這些語(yǔ)言中,指令的設(shè)計(jì)貼近計(jì)算機(jī)的運(yùn)算硬件設(shè)計(jì)埂淮,這讓硬件的運(yùn)行更容易和高效姑隅。在隨后的早期高級(jí)語(yǔ)言中,對(duì)這些指令語(yǔ)句的映射往往成為了設(shè)計(jì)語(yǔ)言的主流方向倔撞。雖然這有利于編譯器的編寫和最終的運(yùn)行效率讲仰,但同時(shí)也阻礙了復(fù)雜程序的設(shè)計(jì)。

舉個(gè)簡(jiǎn)單的例子痪蝇,比如我們有一個(gè)學(xué)生系統(tǒng)鄙陡,記錄了學(xué)生姓名,并用一個(gè)字典記錄了各科目的考試成績(jī):

struct Student {
  let name: String
  let scores: [科目: Int]
}

enum 科目: String, CaseIterable {
  case 語(yǔ)文, 數(shù)學(xué), 英語(yǔ), 物理
}

假設(shè)我們有一些學(xué)生的數(shù)據(jù):

let s1 = Student(
  name: "Jane",
  scores: [.語(yǔ)文: 86, .數(shù)學(xué): 92, .英語(yǔ): 73, .物理: 88]
)
let s2 = Student(
  name: "Tom",
  scores: [.語(yǔ)文: 99, .數(shù)學(xué): 52, .英語(yǔ): 97, .物理: 36]
)
let s3 = Student(
  name: "Emma",
  scores: [.語(yǔ)文: 91, .數(shù)學(xué): 92, .英語(yǔ): 100, .物理: 99]
)

let students = [s1,s2,s3]

我們現(xiàn)在想要檢查 students 里的學(xué)生的平均分躏啰,并輸出第一名的姓名趁矾。使用指令式的方式,依靠運(yùn)算丙唧,循環(huán)和條件語(yǔ)句愈魏,可以給出下面這種解決方案:

var best: (Student, Double)?
for s in students {
  var totalScore = 0
  for key in 科目.allCases {
    totalScore += s.scores[key] ?? 0
    totalScore += s.scores[key] ?? 0
  }
  let averageScore = Double(totalScore) / Double(科目.allCases.count)
  if let temp = best {
    if averageScore > temp.1 {
      best = (s, averageScore)
    }
  } else {
    best = (s, averageScore)
  }
}
if let best = best {
  print("最高平均分: \(best.1), 姓名: \(best.0.name)")
} else {
  print("students 為空")
}

如果第一次讀這段代碼的話,想要了解它到底做了什么或者到底最后會(huì)得到怎樣的結(jié)果想际,可能必須要仔細(xì)閱讀并理解每一行指令培漏。代碼行數(shù)與 bug 多少往往是正相關(guān),而這種開發(fā)方式也會(huì)為代碼維護(hù)帶來(lái)巨大的挑戰(zhàn)胡本。

我們有什么辦法可以減輕開發(fā)者的負(fù)擔(dān)牌柄,讓計(jì)算機(jī)更加“智能”地為我們解決問(wèn)題呢?

聲明式編程

聲明式的編程范式正好站在指令式的對(duì)面:如果說(shuō)指令式是教會(huì)計(jì)算機(jī)“怎么做”侧甫,那么聲明式就是教會(huì)計(jì)算機(jī)“做什么”珊佣。指令式編程是描述過(guò)程,期望程序執(zhí)行以得到我們想要的結(jié)果披粟;而聲明式編程則是描述結(jié)果咒锻,讓計(jì)算機(jī)為我們考慮和組織出具體過(guò)程,最后得到被描述的結(jié)果守屉。

現(xiàn)代語(yǔ)言中惑艇,一般使用函數(shù)式編程或者 DSL 的方式來(lái)實(shí)現(xiàn)聲明式的編程方式。

對(duì)于上面的例子,使用函數(shù)式編程的方式滨巴,可以將它改寫為:

func average(_ scores: [科目: Int]) -> Double {
  return Double(scores.values.reduce(0, +)) / 
         Double(科目.allCases.count)
}

let bestStudent = students
  .map { ($0, average($0.scores)) }
  .sorted { $0.1 > $1.1 }
  .first

在這段代碼中思灌,我們首先將 students 映射為了 (Student, 平均分) 的數(shù)組,然后對(duì)平均分按降序進(jìn)行排序恭取,最后取出排序后的首個(gè)元素泰偿。在這個(gè)過(guò)程中,我們僅僅是用語(yǔ)句描述了我們想要的結(jié)果蜈垮,例如:按規(guī)則進(jìn)行映射耗跛、對(duì)元素進(jìn)我們想要的結(jié)果,例如:按規(guī)則進(jìn)行映射攒发、對(duì)元素進(jìn)行排序等课兄。我們并不關(guān)心代碼在底層具體是如何操作數(shù)組的,而只關(guān)心這段代碼能夠得到我們所描述的結(jié)果晨继。

另一種經(jīng)常用來(lái)實(shí)現(xiàn)聲明式編程的方法是領(lǐng)域特定語(yǔ)言 (DSL),其中一個(gè)典型的代表是 SQL搬俊。SQL 被用在關(guān)系數(shù)據(jù)庫(kù)中紊扬,專門用在結(jié)構(gòu)化查詢這一特定領(lǐng)域,它通過(guò)描述期望的結(jié)果來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行查詢唉擂。上面的例子在 SQL 中的對(duì)應(yīng)語(yǔ)句如下:

select name, avg(score) as avg_score 
from scores group by name order by avg_score;

不論是使用函數(shù)式的方式餐屎,還是使用 DSL 的方式,我們都能夠比較輕松地閱讀代碼玩祟,更快速地理解代碼的意圖腹缩。指令式編程更偏向于是“寫給計(jì)算機(jī)的語(yǔ)言”,而相對(duì)地空扎,聲明式編程則更偏向于“寫給人看的語(yǔ)言”藏鹊。將具體的步驟和工作交給底層,同時(shí)也最大限度避免了由于開發(fā)者的錯(cuò)誤而造成的 bug转锈。

聲明式的UI

使用聲明式的編程方式來(lái)進(jìn)行用戶界面開發(fā)盘寡,在近年來(lái)是頗為熱門和受到歡迎的實(shí)踐方式。當(dāng)前流行的聲明式 UI 的思想撮慨,可以追溯到 Elm 語(yǔ)言的設(shè)計(jì)竿痰。在之后,React 的 Component 和 Flutter 的 Widget 都繼承了這種思想砌溺,這類聲明式 UI 都有如下特點(diǎn):

  • 代表 UI 層的 View 并不是真實(shí)負(fù)責(zé)渲染的傳統(tǒng)意義的視圖層級(jí)影涉,而是一個(gè)“虛擬的”對(duì) View 組織關(guān)系的描述 (聲明)。

  • 決定 UI 的用戶狀態(tài) State 被存儲(chǔ)在某個(gè)或某幾個(gè)對(duì)象中规伐。

  • 用一個(gè)函數(shù)描述 View蟹倾,這個(gè)函數(shù)的輸入?yún)?shù)是 State,即 View = f(State)楷力。

  • 框架在 State 改變時(shí)喊式,調(diào)用上述函數(shù)獲取對(duì)應(yīng)新的 State 的 View孵户,并與當(dāng)前的 View 進(jìn)行差分計(jì)算,并重新渲染更改的部分岔留。

一般來(lái)說(shuō)夏哭,View = f(State) 中的函數(shù) f 是純函數(shù),也就是對(duì)于某個(gè)特定的輸入 State献联,所對(duì)應(yīng)的 View 是確定的竖配,不隨其他變量而改變。我們可以單純地通過(guò)控制和改變 State 來(lái)得到確定的 UI里逆,這是使用聲明式的方法來(lái)構(gòu)建 UI 的基礎(chǔ)进胯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市原押,隨后出現(xiàn)的幾起案子胁镐,更是在濱河造成了極大的恐慌,老刑警劉巖诸衔,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盯漂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡笨农,警方通過(guò)查閱死者的電腦和手機(jī)就缆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谒亦,“玉大人竭宰,你說(shuō)我怎么就攤上這事》菡校” “怎么了切揭?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)脾还。 經(jīng)常有香客問(wèn)我伴箩,道長(zhǎng),這世上最難降的妖魔是什么鄙漏? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任嗤谚,我火速辦了婚禮,結(jié)果婚禮上怔蚌,老公的妹妹穿的比我還像新娘巩步。我一直安慰自己,他們只是感情好桦踊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布椅野。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竟闪。 梳的紋絲不亂的頭發(fā)上离福,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音炼蛤,去河邊找鬼妖爷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛理朋,可吹牛的內(nèi)容都是我干的絮识。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嗽上,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼次舌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起兽愤,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤彼念,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后浅萧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體国拇,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年惯殊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片也殖。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡土思,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忆嗜,到底是詐尸還是另有隱情己儒,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布捆毫,位于F島的核電站闪湾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绩卤。R本人自食惡果不足惜途样,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望濒憋。 院中可真熱鬧何暇,春花似錦、人聲如沸凛驮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宏胯,卻和暖如春羽嫡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肩袍。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工杭棵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人了牛。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓颜屠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鹰祸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甫窟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355