前回、PHPでのBuilderパターンを実装する手順を紹介したが、今回はその続きとしてfluent interfaceを実装したBuilderパターンの実装方法を説明する。
なお、本稿のサンプルコードの完全版はGitHubにて公開している。
fluent interface とは
fluent interfaceとは「流れるようなインターフェイス」という意味で、method chaining(メソッドの連鎖)という小技を使って、可読性の高いコードを実現するメソッドの作り方のことだ。よくドメイン固有言語(DSL)を提供するようなクラスを作るときに使われる。
fluent interfaceを提供しているので有名なのはjQueryかと思う。
$('table')
.find('tr:even').addClass('even').end()
.find('tr:odd').addClass('odd');
fluentじゃないBuilder
前回のEmailBuilderの実装は普通のインターフェイスだったので、セッターを呼ぶたびに$emailBuilder->
と書かなければならなかった。
$emailBuilder = new EmailBuilder();
$emailBuilder->setFrom('alice@example.com');
$emailBuilder->setTo('bob@example.com');
$emailBuilder->setSubject('Hello');
$emailBuilder->setBody('Hello, there.');
$email = $emailBuilder->build();
今回は、次のコードのように、メソッドを連鎖できるようにしてfluent interfaceを実現したいと思う。上のコードと見比べると、繰り返し書かれていたことがなくなったぶん、可読性が良くなったと感じないだろうか?
$email = (new EmailBuilder())
->setFrom('alice@example.com')
->setTo('bob@example.com')
->setCc('carol@example.com')
->setSubject('Hello')
->setBody('Hello, Bob.')
->build();
fluent interfaceの実装法
PHPのfluent interfaceの実装は各メソッドにreturn $this
を追加するだけだ。戻り値の型宣言はself
にする。
final class EmailBuilder
{
// ...略...
public function setFrom(string ...$from): self
{
$this->from = $from;
return $this;
}
public function setTo(string ...$to): self
{
$this->to = $to;
return $this;
}
public function setCc(string ...$cc): self
{
$this->cc = $cc;
return $this;
}
public function setSubject(string $subject): self
{
$this->subject = $subject;
return $this;
}
public function setBody(string $body): self
{
$this->body = $body;
return $this;
}
// ...略...
}
この実装をUMLクラス図に起こすとこうなる:
テストコード
テストコードでもfluent interfaceを使っておけば、return $this
がちゃんと動いていることが確認できる。
use PHPUnit\Framework\TestCase;
final class EmailTest extends TestCase
{
/**
* @test
*/
public function email_builder_usage(): void
{
$email = (new EmailBuilder())
->setFrom('alice@example.com')
->setTo('bob@example.com')
->setCc('carol@example.com')
->setSubject('Hello')
->setBody('Hello, Bob.')
->build();
// The Email object will be like the following:
self::assertSame(['alice@example.com'], $email->getFrom());
self::assertSame(['bob@example.com'], $email->getTo());
self::assertSame(['carol@example.com'], $email->getCc());
self::assertSame('Hello', $email->getSubject());
self::assertSame('Hello, Bob.', $email->getBody());
}
// ...略...
public function test_missing_sender_email_address(): void
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage(
'At least one from-address must be provided'
);
(new EmailBuilder())
->setCc('dummy@address')
->setSubject('dummy_subject')
->setBody('dummy_body')
->build();
}
}
まとめ
- Builderパターンにfluent interfaceを取り入れると、Builderを使う側のコードの可読性が高まる。