15

Joshua Bloch の『Effective Java』を読んでいます。私も PHP で開発を行っており、項目 2 で概説されているビルダー パターンを実装したかったのですが、PHP には内部クラスがありません。製品のコンストラクターをプライベートに保ちながら、PHP でこのパターンを実現する方法はありますか?

4

3 に答える 3

36

PHP は内部クラスをサポートしていないため、製品クラスにはそのインスタンスを作成する public メソッドが必要です。次の PHP クラスを検討してください。

<?php
class NutritionalFactsBuilder {
    private $sodium;
    private $fat;
    private $carbo;

    /**
     * It is preferred to call NutritionalFacts::createBuilder
     * to calling this constructor directly.
     */
    function __construct($s) {
        $this->sodium = $s;
    }

    function fat($f) {
        $this->fat = $f;
        return $this;
    }

    function carbo($c) {
        $this->carbo = $c;
        return $this;
    }

    function getSodium() {
        return $this->sodium;
    }

    function getFat() {
        return $this->fat;
    }

    function getCarbo() {
        return $this->carbo;
    }

    function build() {
        return new NutritionalFacts($this);
    }
}

class NutritionalFacts {
    private $sodium;
    private $fat;
    private $carbo;

    static function createBuilder($s) {
        return new NutritionalFactsBuilder($s);
    }

    /**
     * It is preferred to call NutritionalFacts::createBuilder
     * to calling this constructor directly.
     */
    function __construct(NutritionalFactsBuilder $b) {
        $this->sodium = $b->getSodium();
        $this->fat = $b->getFat();
        $this->carbo = $b->getCarbo();
    }
}

echo '<pre>';
var_dump(NutritionalFacts::createBuilder(10)->fat(23)->carbo(1)->build());
echo '</pre>';
?>

上記の例では、コンストラクターNutritionalFactsが public であることに注意してください。ただし、言語の制約により、パブリック コンストラクターを使用することはまったく悪いことではありません。でコンストラクターを呼び出さなければならないため、NutritionalFactsBuilderをインスタンス化する方法は限られていますNutritionalFacts。それらを比較しましょう:

// NutritionalFacts Instantiation #0
$nfb = new NutritionalFactsBuilder(10);
$nfb = $nfb->fat(23)->carbo(1);
$nf0 = new NutritionalFacts($nfb);

// NutritionalFacts Instantiation #1
$nfb = new NutritionalFactsBuilder(10);
$nf1 = $nfb->fat(23)->carbo(1)->build();

// NutritionalFacts Instantiation #2
$nf2 = NutritionalFacts::createBuilder(10)->fat(23)->carbo(1)->build();

// NutritionalFacts Instantiation #3
// $nf3 = (new NutritionalFactsBuilder(10))->fat(23)->carbo(1)->build();

関数チェーンを最大限に活用するには、「NutritionalFactsインスタンス化 #2」を使用することをお勧めします。

NutritionalFactsインスタンス化 #3」は、PHP 構文の別のニュアンスを示しています。新しくインスタンス化されたオブジェクトでメソッドをチェーンすることはできません。 更新: PHP 5.4.0 では、" Instantiation #3 "の構文がサポートされるようになりました。NutritionalFacts私はまだそれをテストしていません。


コンストラクターを非公開にする

コンストラクターを非公開にすることもできますが、お勧めしません。コンストラクターを非公開にした場合、次のコード スニペットのように、公開の静的ファクトリ メソッドが必要になります。以下のコードを見ると、コンストラクターをプライベートにするためだけにインダイレクションを導入するのではなく、コンストラクターをパブリックにすることもできます。

class NutritionalFacts {
    private $sodium;
    private $fat;
    private $carbo;

    static function createBuilder($s) {
        return new NutritionalFactsBuilder($s);
    }

    static function createNutritionalFacts($builder) {
        return new NutritionalFacts($builder);
    }

    private function __construct($b) {
        $this->sodium = $b->getSodium();
        $this->fat = $b->getFat();
        $this->carbo = $b->getCarbo();
    }
}
于 2012-06-09T17:53:24.317 に答える
4

不変性は良いことであり、間違いなく努力すべきことです。これは、他の言語と同様に PHP にも当てはまります。不変性により、インスタンスが知らないうちに突然変更されることを恐れる必要がないという確信が得られます。

そうは言っても、ビルダー パターンを実装して、内部クラスがなくても不変オブジェクトを構築する簡単な方法があります (現在は PHP 7 で利用可能ですが)。

最初の重要な構成要素は、実際の不変クラスとビルダーの共通基本クラスです。これにより、お互いのプロパティにアクセスできます。フレンド クラスとしても知られるもの、または他の言語では拡張アクセス修飾子を介して解決可能なもので、PHP にはありません。複製機能は制限されていることに注意してください。不変オブジェクトを複製しても意味がありませんが、protected修飾子については後で詳しく説明します。

abstract class NutritionalFactData {

    protected $sodium = 0;
    protected $fat = 0;
    protected $carbo = 0;

    protected function __clone() {}

}

不変クラスは、愚かな例のゲッターとデフォルトのコンストラクターで簡単です。クラス自体のfinal修飾子と、ビルダー クラスをまったく認識していないことに注意してください。

final class NutritionalFacts extends NutritionalFactData {

    public function getSodium() {
        return $this->sodium;
    }

    public function getFat() {
        return $this->fat;
    }

    public function getCarbo() {
        return $this->carbo;
    }

}

次に、実際のビルダーの実装です。不変クラスのインスタンスを直接操作する方法と、build メソッドが呼び出されたときに単純に複製することに注意してください。これにより、ビルダーのセッターへの後の呼び出しが以前に構築されたインスタンスを変更しないことが保証され、そのようなインスタンスの受信者が独自にクローン作成を処理する必要がなくなります。

final class NutritionalFactBuilder extends NutritionalFactData {

    private $nutritional_facts;

    public function __construct() {
        $this->nutritional_facts = new NutritionalFacts;
    }

    public function build() {
        return clone $this->nutritional_facts;
    }

    public function setSodium($sodium) {
        $this->nutritional_facts->sodium = $sodium;
        return $this;
    }

    public function setFat($fat) {
        $this->nutritional_facts->fat = $fat;
        return $this;
    }

    public function setCarbo($carbo) {
        $this->nutritional_facts->carbo = $carbo;
        return $this;
    }

}

完全を期すために、使用例:

var_dump(
    (new NutritionalFactBuilder)
        ->setSodium(21)
        ->setFat(42)
        ->build()
);

これが実行可能な例です。

ビルダーの実装を好きなだけ実装できるようになったことは明らかだと思います。この例では特に必要ありませんが、さらに多くのプロパティが含まれる他の構造を考えることができます。ウィキペディアの (非常に悪い) ビルダー パターンの記事にある車の例のように。既知の自動車カテゴリ用に事前構成されたビルダーが必要になる場合があります。

abstract class CarParts {}

final class Car extends CarParts {}

abstract class CarBuilder extends CarParts {
    abstract public function build(): Car;
}

final class CompactCarBuilder extends CarBuilder {}

final class SportsCarBuilder extends CarBuilder {}

final class RaceCarBuilder extends CarBuilder {}
于 2016-08-11T20:13:20.160 に答える