LoginSignup
17
10

More than 5 years have passed since last update.

PHP: Builderパターンの実装手順 #2【Fluent Interface実装】

Last updated at Posted at 2019-02-13

前回、PHPでのBuilderパターンを実装する手順を紹介したが、今回はその続きとしてfluent interfaceを実装したBuilderパターンの実装方法を説明する。

なお、本稿のサンプルコードの完全版はGitHubにて公開している。

fluent interface とは

fluent interfaceとは「流れるようなインターフェイス」という意味で、method chaining(メソッドの連鎖)という小技を使って、可読性の高いコードを実現するメソッドの作り方のことだ。よくドメイン固有言語(DSL)を提供するようなクラスを作るときに使われる。

fluent interfaceを提供しているので有名なのはjQueryかと思う。

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クラス図に起こすとこうなる:

image.png

テストコード

テストコードでも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を使う側のコードの可読性が高まる。

関連

17
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10