Tags: php5 refactoring nullobject
剛在重構一組類別的程式碼時,突然想到 Martin 在《敏捷軟體開發原則、樣式與實務》一書中提到的一個編程技巧,就是在失敗狀況時回傳 NullObject ,避免行為調用者用 if
或 try
處理失敗狀況,影響程式可讀性。
我重構中的類別程式碼,基本上是一個聚合類別,它包含了其他類別的個體。此聚合類別提供一個方法 get()
,以取得它所包含的個體。外部調用 get()
後取得內容個體後,立即呼叫該個體的一個方法。
原本的程式碼,其架構大略如下所示。
程式碼的語意是調用 $c->get()
取得內容個體,接著調用取出物的 output()
行為。問題在於這段程式碼並未考慮找不到指定內容個體的情形,故第27行的 $c->get('b')->output()
將會引發調用不存在個體之方法的執行錯誤。直覺上,我們的解決的方式就是修改調用行為的這一段程式碼,加上 if
或 try
的錯誤處理流程。
但這在大型專案或分工團隊中可能會有副作用。例如甲是負責基礎類別的程序員,也就是負責寫上例第1到23行程式碼的人;乙是負責應用流程的程序員,也就是寫第24到27行程式碼的人。現在甲要重構基礎類別,並可能改變行為回傳結果時,按直覺的方式,我們將牽連乙去修改他的程式碼。這就是一種不良副作用。
而 Martin 所說的編程技巧,則是在碰到找不到指定個體時就回傳一個 NullObject,使得調用者可以不用修改程式碼。這個編程技巧用 PHP 實作很簡單,只要利用 PHP5 的 magic method 就能輕鬆實現。如下所示。
NullObject and magic method
我們重構時,增加一個 NullObject 類別,這類別就是什麼事都不做。所以我們再利用 magic method 的 __call()
實作一個什麼都不做的泛用行為。這可以免除 PHP 發出找不到指定行為的執行錯誤。最後,我們只要再把 get()
回傳 false 的動作改成回傳 NullObject 即可。
就這樣,我們完成了重構動作。而調用者的程式碼不必進行任何修改,就無聲無息地略過了錯誤處理流程並保持程式碼簡潔的語意。
Java style
附帶一提。這個編程技巧也可以用來檢視程式語言的動態性對我們的影響。假如我們不用 PHP5 提供的 magic method ,那麼我們就要採用類似 Java 的編程風格來重構,範例如下。
昨天寫下這一段話,第二天就後悔了。因為我想到 Ruby, JavaScript 並沒有 PHP5 __call 的 magic method 機制。而 Ruby, JavaScript 的動態性卻又比 PHP 還優秀。所以用 magic method 機制評定程式語言的動態能力,似乎不太適當。Magic method 果然 magic...
在 Java 編程風格下,我們需要先定義一個具有 output()
行為的介面I。再為 C 類別專門定義一個實作了介面I 的 CNullObject 類別。
這種受限於程式語言動態能力所帶來的編程風格,最顯而易見的累贅就是要定義許多 xNullObject,而它們所作事都一樣: 不做任何事。結果我們為了不做任何事的類別複製了許多重複的程式碼。
樂多舊網址: http://blog.roodo.com/rocksaying/archives/6028145.html
樂多舊回應