開發application時,常常會有大量的呈現(presentation)邏輯。
像是日期格式、金額格式(是否穿插逗號)、名稱(是否需要大寫)等等。
把這些logic寫在model的話,很容易就會導致model過胖。
舉例來說,如果文章model在不同地方需要呈現日期、日期時間、西方格式、台灣格式,
model就會長這樣(以Laravel Eloquent舉例):
class Article extends Eloquent { public function getDate(){/*...*/} // 呈現給台灣地區的時間格式 public function getTaiwaneseDateTime(){/*...*/} // 呈現給歐美地區的時間格式 public function getWesternDateTime(){/*...*/} public function getTaiwaneseDate(){/*...*/} public function getWesternDate(){/*...*/} }
要將這類presentation logic抽出來,許多框架採用了「helper function」的作法,
也就是定義好幾個functions用來處理呈現邏輯。
除了helpers的作法之外,還有一個非常簡單有效的方法。
也就是使用Decorator pattern來封裝出presenter,像這樣:
class ArticlePresenter { protected $article; public function __construct(Article $article) { $this->article = $article; } // 呈現給台灣地區的時間格式 public function getTaiwaneseDateTime(){ return date('Y-m-d', $this->article->created_at); } // 呈現給歐美地區的時間格式 public function getWesternDateTime(){/*...*/} public function getTaiwaneseDate(){/*...*/} public function getWesternDate(){/*...*/} }
概念上只是把原本的model從constructor傳進去,原本的function搬過去,
把$this改寫成$this->article即可。
然而,這樣會導致view裡面需要初始化物件:
@foreach($articles as $article) 發文日期:{{ $presenter->getTaiwaneseDate() }} @endforeach
在view裡面做這種事,怎麼看都是很醜的。
多寫一個function即可改善:
class Article extends Eloquent { public function present() { return new ArticlePresenter($this); } }
view內會變成這樣:
@foreach($articles as $article) 發文日期:{{ $article->present()->getTaiwaneseDate() }} @endforeach
新的問題來了:多次呼叫present會浪費記憶體。
可以這樣改善:
class Article extends Eloquent { protected $presenterInstance; public function present() { if ( ! $this->presenterInstance) { $this->presenterInstance = new ArticlePresenter($this); } return $this->presenterInstance; } }
多個model都使用presenter的話,這段code會重複。善用PHP trait可以解決這個問題:
trait PresentableTrait { protected $presenterInstance; public function present() { // ... if ( ! $this->presenterInstance) { $this->presenterInstance = new $this->presenter($this); } return $this->presenterInstance; } }
Laravel社群至少有3套成熟的presenter實作:
https://github.com/laracasts/Presenter
https://github.com/robclancy/presenter
https://github.com/laravel-auto-presenter/laravel-auto-presenter
有的功能豐富、有的比較陽春,但是概念上一樣,都是decorator pattern的應用。
下次你的application包含大量presentation logic時,不妨試試看這個作法。
(Photo via Susanne Nilsson, CC licensed)