1. 程式人生 > >面向過程,面向物件,函式式對同一個問題的思考方式

面向過程,面向物件,函式式對同一個問題的思考方式

我之所以對函式式程式碼感興趣是因為函式式程式碼富有表現力,可以使用簡短、緊湊的程式碼完成工作,同時能對特定的問題給出優雅的解決方案。現代的程式語言不約而同的朝著面向物件、函式式、動態、解釋執行的方向發展,例如Ruby,Swift。而另一些語言則更加強調函數語言程式設計,如F#,Scala,這種語言有著強大的型別推斷系統,編寫的程式碼潔程度則令人歎為觀止。

在F#編寫一個兩個數相加的函式,在F# Interactive中輸入:

C#
1 let add num1 num2=num1*num2;;

F# Interactive為我們推斷了這個函式型別:val add : num1:int -> num2:int -> int,表示add有兩個int型別的引數得到了1個int型別。

函式當作引數:

C#
123456 //C#privateintTwice(intinput,Funcf){returnf(f(input));}varresult=Twice(2,n=>n*n);

使用F#則只需要非常簡潔的一個函式宣告:

C#
123456 >let twice(input:int)f=f(f(input));;val twice:input:int->f:(int->int)->int>twice2(funn->n*n);;val it:int=16

val twice : input:int -> f:(int -> int) –> int 這句話則是F# Interactive給出的推斷:twice函式需要一個int引數和一個(int->int)的函式作為引數,返回一個int.

這兩個例子僅僅是熱身,並不是本篇部落格的重點,所以你覺得前兩個例子很無聊或者沒太看明白請繼續看下面的總結。

場景:某種活動會有一個日程安排(Schedule),日程安排有3中型別,只舉辦一次(Once),每天一次(Daily),每週一次(Weekly)。活動會根據日程安排(Schedule)的型別不同具有不同的宣傳內容,不同的延期舉行策略。

你對於這樣的場景會有怎麼樣的思考呢?

一、面向過程型別的編碼方式

面向過程型別的編碼是需求的直譯過程,程式碼會寫成這樣:

1.顯示活動的宣傳內容:

C#
1234567891011121314151617 publicvoidShowScheduleDescriptions(){switch(ScheduleType){caseScheduleType.Once:Console.WriteLine("this is once activity");break;caseScheduleType.Daily:Console.WriteLine("this is daily activity");break;caseScheduleType.Weekly:Console.WriteLine("this is weekly activity");break;default:thrownewInvalidOperationException("unsupported schedule");}}

這樣的程式碼初次看起來沒什麼問題,實際存在兩個危險訊號:

  • 違反開放封閉(OCP)原則,如果有一天需要加入一種Monthly型別,無疑需要修改這個方法;
  • 這樣的程式碼風格會讓接下來的開發者不假思索的進行延續,比方說需要根據不同的活動型別延期活動;

2. 延期活動:

C#
1234567891011121314151617 publicvoidDelaySchedule(){switch(ScheduleType){caseScheduleType.Once:Console.WriteLine("Delay one hour");break;caseScheduleType.Daily:Console.WriteLine("Delay one day");break;caseScheduleType.Weekly:Console.WriteLine("Delay one week");break;default:thrownewInvalidOperationException("unsupported schedule");}}

這樣的代格違反了DRY原則,相同的程式碼框架卻無法重用。

二、面向物件的編碼方式

對於一個有經驗的OO開發者,一旦看到switch,if(type=typeof(…))之類的程式碼馬上會提高警惕,是不是有一些抽象型別沒有被找出來?在這個例子中則會找出下面的抽象:

C#
1234567891011121314151617181920212223242526272829303132333435363738 publicclassSchedule{publicvirtualvoidShowShowScheduleDescriptions(){}publicvirtualvoidDelaySchedule(){}}publicclassOnceSchedule:Schedule{publicoverridevoidShowShowScheduleDescriptions(){Console.WriteLine("this is once activity");}publicoverridevoidDelaySchedule(){Console.WriteLine("Delay one hour");}}publicclassDailySchedule:Schedule{publicoverridevoidShowShowScheduleDescriptions(){Console.WriteLine("this is daily activity");}publicoverridevoidDelaySchedule(){Console.WriteLine("Delay daily day");}}//... other schedule

這樣的程式碼很好的解決了面向過程程式碼的兩個問題,看起來更加具有擴充套件性,隨著新型別的Schedule引入,舊的程式碼完全不用改動。

當然事情也不是絕對的,什麼情況下需要改動舊程式碼呢?當需要擴充套件Schedule的行為的時候,例如需求升級,不同的Schedule具有不同的舉辦方式,我們不得不在每種Schedule中加入一個 void Hold()方法。

三、函式式解決方案

函式式語言則使用可區分聯合和模式匹配來處理此類問題。

定義一個Schedule可區分聯合:

C#
1234 type Schedule=|Once of DateTime|Daily of DateTime*int|Weekly of DateTime*int

這個型別既說明了Schedule有三個不同的型別,同時定義了三種類型分別具有的資料結構。像是Enum和類的綜合體,但是又顯得特別精緻。

1.顯示活動的宣傳內容,使用了模式匹配:

C#
12345 let ShowShowScheduleDescriptions schedule=match schedule with|Once(DateTime)->printfn"this is once activity"|Daily(DateTime,int)->printfn"this is daily activity"|Weekly(DateTime,int)->printfn"this is weekly activity"

這個方法類似於switch…case,但是通過匹配可區分聯合來實現,而不是通過一個顯示的Enum來實現。

2. 延期活動:

C#
12345 let DelaySchedule schedule=match schedule with|Once(DateTime)->printfn"Delay one hour"|Daily(DateTime,int)->printfn"Delay one day"|Weekly(DateTime,int)->printfn"Delay one week"

函數語言程式設計的解決方案認為可以很方便的新增新的行為,例如增加新的行為:Hold()。通過定義可區分聯合和模式匹配來完成編碼,整個解決方案像是面向過程和麵向物件的一種結合體,但是側重點不同,實現的程式碼也更加精緻。