程式設計的一些抽象核心

雪山飛豬發表於2020-09-28

一、程式、演算法

本質

程式=演算法+數結結構
演算法=邏輯+控制

程式=演算法+數結結構,這個是眾所周知了,演算法這個解釋則比較清麗脫俗:1.邏輯 2. 控制。

  • 邏輯用來解決實際的問題, 控制決定用什麼策略來解決問題,邏輯是真正意義上的解決問題的演算法
  • 控制是一個程式流轉的方式,即程式執行的方式,並行還是序列,同步還是非同步,排程不同執行路徑或模組,資料之間的儲存關係。

邏輯是是處理什麼,控制是怎麼做,溝通方式是資料結構

舉例

表單驗證經常的做法是

function check(data) {
    名字 = get(data, 名字)
    if (名字為空 || 名字長度小於3) {
        return 驗證失敗,名字非法
    } 
    密碼 = get(data,密碼)
    if (密碼為空 || 密碼長度小於8) {
        return 驗證失敗,密碼非法
    } 
    郵箱 = get(data,郵箱)
    if (郵箱為空 || 郵箱格式不正確) {
        return 驗證失敗,郵箱非法
    } 
    ...
    return 驗證通過; 
}

這個函式最終邏輯是驗證輸入資料的合法性,但是這樣其實就是把控制和邏輯耦合在一起了,隨著驗證欄位的增多,這個函式會越來越長,最終造成可讀性越來越差,越來越難維護

更好的分離兩者的方法是,定義一個表單驗證器,如下

var rules = {
     名字: ["長度大於3"],
     密碼: ["長度大於8"]
     郵箱: ["email格式"]
    ...
    ]
};
 if 失敗結果=checkRules(rules).執行(); 失敗結果不為空 {
    return 驗證失敗,失敗結果
}
return 驗證通過 

這樣通過描述驗證規則,得到了一種控制和邏輯之間的解耦,不用再在check裡邊各種if else了。

絕大多數程式複雜混亂的根本原因是業務邏輯和控制邏輯的耦合

二、物件導向、設計模式

物件導向、設計模式的重點就是:

  • 使用介面抽象了具體的實現類,這樣其它類耦合的是介面而不是實現類,這就是多太,增加了程式的擴充套件性
  • 介面也就是一種協議,就像HTTP協議一樣,瀏覽器和後端的程式都依賴於這一種協議,而不是具體的實現(如果是依賴具體實現,那麼瀏覽器就要依賴後端的程式語言或中介軟體了,這就太噁心了)。於是,瀏覽器和後端的程式就完全解除依賴關係,而去依賴一個標準的協議了,這也是Ioc/DIP的本質。

23個設計模式基本就是說了兩個物件導向的核心理念:面向介面程式設計、組合優於繼承

面向介面程式設計

  • 使用者不需要知道資料型別、結構、演算法的細節
  • 使用者不需要知道實現細節,只需要知道提供的介面

組合優於繼承

繼承需要給子類暴露一些父類的設計和實現細節,父類實現的改變會造成子類也需要改變,而組合則不存在這樣的弊端。
我們以為繼承主要是為了程式碼重用,但實際上在子類中需要重新實現很多父類的方法,繼承更多的應該是為了多型。

三、依賴倒置和控制反轉 (IoC/DIP)

簡單說就是要依賴於抽象介面,不要依賴於具體實現
這就是控制反轉,開關從以前裝置的專用開關,轉變到了控制電源的開關,而以前的裝置要反過來依賴開關廠宣告的電源介面

從以前的以物易物,變成了依賴“錢”,所有的商品都依賴這個“錢”的交易協議,不用再互相依賴了,整個世界的動作就簡單了很多

開關和燈

有一個開關要控制一個燈的開和關,最直接的方法就是把開關和燈做到一起。
但是,如果有一天,我們發現這個開關可能還要控制別的不僅是燈泡的東西,就會發現開關耦合了燈泡這種類別,非常不得擴充套件。
解決方案就是,造開關工廠根據不需要關心要控制的東西是什麼,只做一個開關,功能就是把電接通和斷開,不管是手動的、聲控的還是光控、遙控的,開關廠和電燈廠依賴於一個標準的通電和斷電的介面。
造燈泡的工廠也不關心你用什麼樣的開關,只管把燈的電源介面做出來,開關廠和電燈廠依賴於一個標準通電和斷電的介面。

證券交易所

在交易的過程中,賣家向買家賣東西,一手交錢一手交貨,基本是要見面的,這個時候銀行出來做擔保,買家先把錢打到銀行,銀行讓賣家發貨,買家驗貨後,銀行再把錢打給賣家。
這就是反轉控制,買賣雙方把對對方的直接依賴和控制,反轉到了讓對方來依賴一個標準的交易模型的介面。

相關文章