2012-06-09 12 views
12

He estado leyendo a través de Java efectivo por Joshua Bloch. También desarrollo en PHP y quería implementar el builder pattern outlined in item 2, pero PHP no tiene clases internas. ¿Hay alguna forma de lograr este patrón en PHP, manteniendo el constructor para el producto privado?Patrón PHP Builder sin clases internas

+0

Si bien la pregunta tiene una solución, el valor de dicha implementación se reduce en gran medida. PHP no se basa en la inmutabilidad, por lo que la única ventaja serían los métodos encadenados. El uso del patrón JavaBeans es la solución aceptada en PHP. – danidacar

Respuesta

27

Desde PHP does not support inner classes, debe haber un método público en la clase de producto que crea una instancia de la misma. Tenga en cuenta las siguientes clases 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>'; 
?> 

Tenga en cuenta que en el ejemplo anterior el constructor de NutritionalFacts es público. Sin embargo, debido a las limitaciones del lenguaje, tener un constructor público no es para nada malo. Dado que uno debe llamar al constructor con un NutritionalFactsBuilder, solo hay un número limitado de formas de crear instancias de NutritionalFacts. Vamos a comparar ellos:

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

para aprovechar la función de encadenamiento en toda su extensión, "NutritionalFacts instanciación # 2" es el uso preferido.

" NutritionalFacts Instantiation # 3" muestra otro matiz de la sintaxis de PHP; one cannot chain a method on a newly instantiated object. Actualización: En PHP 5.4.0, ahora hay support for the syntax en "NutritionalFacts Instantiation # 3". Aunque no lo he probado todavía.


Hacer el constructor privado

Se podría hacer que el constructor privado, pero yo no lo recomendaría. Si el constructor se hizo privado, sería necesario un método de fábrica público, estático, como en el siguiente fragmento de código. Si miramos el código siguiente, también podríamos hacer que el constructor sea público en lugar de introducir un direccionamiento indirecto solo para hacer que el constructor sea privado.

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

Esto es perfecto. Gracias, respondiste completamente mi pregunta. – Jackson

+0

Además, acaba de notar que puede encadenar un método en un objeto recién instanciado en PHP 5.4: http://docs.php.net/manual/en/migration54.new-features.php – Jackson

+0

@JacksonOwens Eso es increíble. Agregue lo que acaba de decir como respuesta a http://stackoverflow.com/questions/2188629. Creo que eso sería útil para otros. – creemama

1

En la descripción de Gang of Four del patrón Builder, no encontrará ningún requisito para una clase interna. La característica clave es la relación total entre el Director y la interfaz del Constructor que proporciona un "anteproyecto" para armar una serie de implementaciones del Producto.

Usted puede encontrar gran cantidad de ejemplos del patrón de PHP Constructor aquí:

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

Cheers, Bill

1

inmutabilidad es buena y sin duda algo que esforzarse, esto se aplica a PHP, ya que hace a cualquier otro idioma no importa qué. La inmutabilidad te da certeza de que no tienes que temer que la instancia de repente muta sin que lo sepas.

Dicho esto, hay una manera fácil de implementar el patrón del generador para construir objetos inmutables incluso sin clases internas (aunque está disponible ahora con PHP 7).

El primer bloque de construcción importante es una clase base común para la clase real inmutable y el constructor. Esto les permite acceder a las propiedades de los demás. Algo que también se conoce como clases de amigos o solucionables a través de modificadores de acceso extendido en otros idiomas, algo que PHP no tiene.Tenga en cuenta que la capacidad de clonación está restringida, no tiene sentido clonar objetos inmutables, pero más sobre el modificador protected más adelante.

abstract class NutritionalFactData { 

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

    protected function __clone() {} 

} 

La clase inmutable es directa con getters de ejemplo estúpidos y un constructor privado. Tenga en cuenta el modificador final para la clase en sí y que no tiene conocimiento de la clase de constructor.

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

} 

Ahora la implementación real del constructor. Observe cómo operamos directamente en una instancia de la clase inmutable y que simplemente la clonamos cuando se llama al método de compilación. Esto garantiza que las llamadas posteriores a los instaladores del generador no alterarán las instancias que se crearon previamente y asegura que ningún receptor de dicha instancia tenga que encargarse de la clonación por sí mismo.

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

} 

Para completar un ejemplo de uso:

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

Creo que es obvio que ahora podemos aplicar como muchas implementaciones constructor como nos gusta. No es realmente necesario para este ejemplo, pero podemos pensar en otros constructos donde están involucradas muchas más propiedades. Como el ejemplo del coche dado en (el muy malo) artículo del patrón de construcción de Wikipedia. Es posible que deseemos tener constructores preconfigurados para categorías de automóviles conocidas.

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