開發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)