2012-06-09 9 views
12

Ich habe durch Effektive Java von Joshua Bloch gelesen. Ich entwickle auch in PHP und wollte die builder pattern outlined in item 2 implementieren, aber PHP hat keine inneren Klassen. Gibt es eine Möglichkeit, dieses Muster in PHP zu erreichen und den Konstruktor für das Produkt privat zu halten?PHP Builder Muster ohne innere Klassen

+0

Während die Frage eine Lösung hat, ist der Wert einer solchen Implementierung stark reduziert. PHP ist nicht um Unveränderlichkeit herum aufgebaut, so dass der einzige Vorteil die verketteten Methoden wären. Die Verwendung des JavaBeans-Musters ist die akzeptierte Lösung in PHP. – danidacar

Antwort

27

Seit PHP does not support inner classes muss eine öffentliche Methode für die Produktklasse vorhanden sein, die eine Instanz davon erstellt. Betrachten Sie die folgenden PHP-Klassen:

<?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>'; 
?> 

Beachten Sie, dass in dem obigen Beispiel der Konstruktor von NutritionalFacts öffentlich ist. Aufgrund der Einschränkungen der Sprache ist es jedoch nicht schlecht, einen öffentlichen Konstruktor zu haben. Da man den Konstruktor mit einer NutritionalFactsBuilder aufrufen muss, gibt es nur eine begrenzte Anzahl von Möglichkeiten, NutritionalFacts zu instanziieren. Vergleichen wir sie:

// 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(); 

Funktion zu nutzen, in vollem Umfang Chaining „NutritionalFacts Instantiierung # 2“ ist die bevorzugte Verwendung.

" NutritionalFacts Instantiierung # 3" zeigt eine andere Nuance der PHP-Syntax; one cannot chain a method on a newly instantiated object. Update: In PHP 5.4.0 gibt es jetzt support for the syntax in "NutritionalFacts Instanziierung # 3." Ich habe es aber noch nicht getestet.


Making the Constructor Privat

Sie könnten der Konstruktor privat machen, aber ich würde es nicht empfehlen. Wenn der Konstruktor privat gemacht würde, wäre eine öffentliche, statische Factory-Methode erforderlich, wie im folgenden Code-Snippet. Wenn wir uns den folgenden Code ansehen, können wir den Konstruktor auch öffentlich machen, anstatt eine Indirektion einzuführen, nur um den Konstruktor privat zu machen.

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(); 
    } 
} 
+0

Das ist perfekt. Danke, du hast meine Frage vollständig beantwortet. – Jackson

+0

Auch, gerade bemerkt, dass Sie eine Methode auf einem neu instanziierten Objekt in PHP 5.4 verketten können: http://docs.php.net/manual/en/migration54.new-features.php – Jackson

+0

@JacksonOwens Das ist genial. Fügen Sie das, was Sie gerade gesagt haben, als Antwort auf http://stackoverflow.com/questions/2188629 hinzu. Ich denke, das wäre für andere nützlich. – creemama

1

In der Beschreibung der Gang of Four des Builder-Musters finden Sie keine Anforderung für eine innere Klasse. Das Schlüsselmerkmal ist die Aggregatbeziehung zwischen der Director- und der Builder-Schnittstelle, die einen "Blueprint" zum Zusammenstellen einer Reihe von Produktimplementierungen bereitstellt.

Sie können viele Beispiele für die Muster PHP Builder finden Sie hier:

http://www.php5dp.com/category/design-patterns/builder/

Cheers, Bill

1

Unveränderlichkeit ist gut und auf jeden Fall etwas zu streben, gilt dies als es PHP tut zu jeder anderen Sprache, egal was. Unveränderlichkeit gibt Ihnen Gewissheit, dass Sie nicht befürchten müssen, dass die Instanz plötzlich mutiert, ohne dass Sie es wissen.

Es gibt eine einfache Möglichkeit, das Builder-Muster zu implementieren, um unveränderliche Objekte auch ohne innere Klassen zu erstellen (obwohl jetzt mit PHP 7 verfügbar).

Der erste wichtige Baustein ist eine gemeinsame Basisklasse für die eigentliche unveränderliche Klasse und den Builder. Dies ermöglicht ihnen, auf die anderen Eigenschaften zuzugreifen. Etwas, das auch als Freundesklassen oder durch erweiterte Zugriffsmodifizierer in anderen Sprachen lösbar ist, etwas, das PHP nicht hat.Beachten Sie, dass die Klon-Fähigkeit eingeschränkt ist. Es macht keinen Sinn, unveränderliche Objekte zu klonen, sondern später mehr über den Modifikator protected.

abstract class NutritionalFactData { 

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

    protected function __clone() {} 

} 

Die unveränderliche Klasse ist geradlinig mit dummen Beispielgettern und einem privaten Konstruktor. Beachten Sie den Modifizierer final für die Klasse selbst und dass die Builder-Klasse überhaupt nicht bekannt ist.

final class NutritionalFacts extends NutritionalFactData { 

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

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

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

    private function __construct() {} 

} 

Jetzt die eigentliche Builder-Implementierung. Beachten Sie, wie wir direkt auf einer Instanz der unveränderlichen Klasse arbeiten und dass wir sie einfach klonen, wenn die Build-Methode aufgerufen wird. Dies stellt sicher, dass spätere Aufrufe an die Setter des Builders die zuvor erstellten Instanzen nicht ändern und sicherstellen, dass kein Empfänger einer solchen Instanz sich selbst um das Klonen kümmern muss.

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; 
    } 

} 

Für Vollständigkeit ein Anwendungsbeispiel:

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

Ich denke, es ist offensichtlich, dass wir jetzt so viele Builder Implementierungen umsetzen können, wie wir wollen. Für dieses Beispiel wird es nicht wirklich benötigt, aber wir können uns andere Konstrukte vorstellen, bei denen viel mehr Eigenschaften beteiligt sind. Wie das Auto-Beispiel, das auf dem (sehr schlechten) Erbauermusterartikel von Wikipedia gegeben wird. Wir möchten vielleicht vorkonfigurierte Builder für bekannte Fahrzeugkategorien haben.

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 {}