不同維度的拆分:物件導向和函式式的區別

凌霄光發表於2018-12-23

物件導向是什麼

我們學習程式設計的時候,學完基礎的語法就是學物件導向了吧。對於物件導向每個人都應該有一些理解,我這裡講一下我的理解。

世間萬事萬物皆為物件,物件包括屬性和行為。我們只需要把我們所關心的物件、屬性、行為抽象出來就好了。比如兔子,如果我們關心的是龜兔賽跑的過程,那麼我們只需要抽象出他速度、耐力等相關屬性,他只需要跑和休息的方法,但如果我們是做生物研究的話,可能要抽象出毛髮長度、耳朵形狀等等屬性,需要叫、跑、跳等方法。具體的封裝粒度和形式與所關心的過程有關。

不同的語言對於物件導向的實現不一樣,有的是類式的,如Java、C++,有的是原型式,比如JavaScript,這只是不同的實現方式。思想和抽象的過程還是一樣的。

物件導向的擴充套件方式是組合和繼承,包括屬性和方法兩部分的複用。

函式式是什麼

有個權威的定義是: 程式 = 邏輯 + 資料,邏輯部分可以拆分成一個個的函式,劃分函式是為了複用和邏輯清晰,就像劃分ui元件的目的一樣。而如果一個函式與上下文耦合了,或者內部有一個可變的狀態,那麼它是很難複用的,因為你要先把他需要的環境配齊了,你才能去用。而沒有內部狀態和對外部環境依賴的函式是複用性很高的,叫做純函式。純函式和不純的函式的區別就像綠色軟體和需要安裝的軟體的區別一樣,一個是依賴環境的,一個是對環境無依賴的。可能不那麼準確,但可以直觀感受下純函式的好處。

純函式因為對內部狀態和外部環境都沒有依賴,所以一個輸入值,對應著唯一的輸出值,所以也可以把它當成一個變數。而變數是可以進行算術、邏輯、比較等運算的,對應到純函式也就有了算術運算函式、邏輯運算函式、比較運算函式等。

純函式是不能有內部狀態、也不能依賴上下文的,但確實有一些資料是在上下文中,這時需要再包一層函式,叫做Monad,而應用具體函式到這個被包裹的值得函式叫做Functor。

複用性很高的純函式,根據具體邏輯的需求進行組合,比如序列的呼叫(pipe、compose),來完成具體的過程,組合需要介面統一,就像機械零件一樣,所以統一成一個引數的比較好組合,函式引數歸一化叫做currify。通過組合一系列的單個函式,完成不同邏輯過程,這就叫函式式。資料最後傳入組合好的函式。

如下就是一個函式式的例子,通過組合把一系列過程封裝到一個函式內,然後把資料傳入這個函式就能完成整個過程。就像水流過管道一樣,更直觀點的感受可以說是先搭好了多米諾骨牌,然後把第一張骨牌推倒。這就是函式式的形式:組合好了函式管道,資料最後傳入。

// 提取 tasks 屬性
var SelectTasks = R.prop('tasks');

// 過濾出指定的使用者
var filterMember = member => R.filter(
  R.propEq('username', member)
);

// 排除已經完成的任務
var excludeCompletedTasks = R.reject(R.propEq('complete', true));

// 選取指定屬性
var selectFields = R.map(
  R.pick(['id', 'dueDate', 'title', 'priority'])
);

// 按照到期日期排序
var sortByDueDate = R.sortBy(R.prop('dueDate'));

// 合成函式
var getIncompleteTaskSummaries = function(membername) {
  return fetchData().then(
    R.pipe(
      SelectTasks,
      filterMember(membername),
      excludeCompletedTasks,
      selectFields,
      sortByDueDate,
    )
  );
};
複製程式碼

物件導向和函式式的區別是什麼

物件導向是比較常見的思路,而函式式也是一種程式設計的思路,或者說這是兩種程式設計正規化。這兩者的關係其實我們身邊也能找到對應的。

比如目錄結構的劃分可以有兩種維度,一種是先按程式碼功能劃分再按業務模組劃分:

components
   user-login
   goods-list
pages
    user-login
    goods-list
store
   user-login
    goods-list
utils
assets
複製程式碼

一種是先按業務模組劃分再按程式碼功能劃分:

user-login
    components
    pages
    store
    assets
    utils
goods-list
    components
     pages
    store
    assets
    utils
複製程式碼

這兩種方式哪種更好呢,其實需要看具體情況,如果業務模組特別多,每個模組差別可能比較大,那麼第二種方式更好,如果業務模組比較少,且基本都是一樣的,那麼第一種方式比較好。

這其實就和函式式與物件導向的區別一樣,程式 = 資料 + 邏輯, 物件導向就像第二種方式,把資料和邏輯封裝到了一起,作為整體來複用和組合,而函式式則是把資料和邏輯分開,邏輯部分通過函式的組合來複用,之後再傳入資料。

所以,函式式和麵向物件也就沒有哪個更好一說,如果是資料和邏輯的關係耦合緊密,那麼還是封裝成物件來複用更好,如果邏輯比較獨立,那麼邏輯部分用函式式來拆分和複用更好。這只是兩種劃分維度。

一般來說遊戲中用物件導向比較多,因為他們涉及到的物件都是資料和方法耦合特別緊密的,比如子彈,你如果用函式式的方式把子彈的資料和子彈運動的函式分開,也沒啥意義,一是因為子彈運動的函式對別的模組來說沒有多大的複用和組合的價值,二是分開這兩部分可能會導致程式很難理解。後端的程式碼也一般是物件導向比較多,但是一般後端的Model層都是貧血模型,就是運算元據的邏輯和資料實體類是分開封裝的,我覺得這樣的話用函式式可能會更好。函式式用的最多的領域還是科學計算領域,因為這些計算過程是完全的與資料無關的,也叫pointfree的。

總結

物件導向是以所研究的業務實體為角度來抽象和封裝對應的屬性和方法,以實體的方式來組合和複用,組合方式有繼承、組合等。而函式式是另一個維度的劃分,把資料和邏輯分開,對邏輯部分劃分成容易複用的純函式,同時提供一系列的算術、邏輯、關係運算函式,之後通過函式組合來複用。

這兩種方式只是不同的劃分角度,就像目錄結構的劃分一樣。一般資料和邏輯耦合很高的業務過程會用物件導向,比如遊戲開發,而邏輯和資料關係不緊密的(pointfree的)會用函式式,比如科學計算。

相關文章