Merge branch 'factories-factories-everywhere'

This commit is contained in:
Trismegiste 2013-05-11 01:08:07 +02:00
commit 5f457c2701
29 changed files with 725 additions and 60 deletions

View File

@ -1,42 +1,43 @@
<?php
namespace DesignPatterns;
namespace DesignPatterns\AbstractFactory;
/**
* Abstract Factory pattern
*
* Purpose:
* to create series of related or dependant objects without specifying their concrete classes,
* usually the created classes all implement the same interface
*
* Examples:
* - A Factory to create media in a CMS: classes would be text, audio, video, picture
* - SQL Factory (types are all strings with SQL, but they vary in detail (tables, fields, etc.))
* - Zend Framework: Zend_Form::createElement() creates form elements, but you could also call new T
* TextElement() instead
* - an abstract factory to create various exceptions (e.g. Doctrine2 uses this method)
*
* usually the created classes all implement the same interface. The client of the abstract
* factory does not care about how these objects are created, he just knows they goes together.
*
* Sometimes also known as "Kit" in a GUI libraries.
*
* This design pattern implements the Dependency Inversion Principle since
* it is the concrete subclass which creates concrete components.
*
* In this case, the abstract factory is a contract for creating some components
* for the web. There are two components : Text and Picture. There is two ways
* of rendering : HTML or JSON.
*
* Therefore 4 concretes classes, but the client just need to know this contract
* to build a correct http response (for a html page or for an ajax request)
*/
abstract class AbstractFactory
{
/**
* @static
* Creates a text component
*
* @param string $content
* @return AbstractFactory\Text
* @return Text
*/
public static function createText($content)
{
return new AbstractFactory\Text($content);
}
abstract public function createText($content);
/**
* @static
* Createss a picture component
*
* @param string $path
* @param string $name
* @return AbstractFactory\Picture
* @return Picture
*/
public static function createPicture($path, $name = '')
{
return new AbstractFactory\Picture($path, $name);
}
abstract public function createPicture($path, $name = '');
}

View File

@ -0,0 +1,23 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Html;
use DesignPatterns\AbstractFactory\Picture as BasePicture;
/**
* Picture is a concrete image for HTML rendering
*/
class Picture extends BasePicture
{
// some crude rendering from HTML output
public function render()
{
return sprintf('<img src="%s" title="$s"/>', $this->_path, $this->_name);
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Html;
use DesignPatterns\AbstractFactory\Text as BaseText;
/**
* Text is a concrete text for HTML rendering
*/
class Text extends BaseText
{
public function render()
{
return "<div>" . htmlspecialchars($this->_text) . '</div>';
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory;
/**
* HtmlFactory is a concrete factory for HTML component
*/
class HtmlFactory extends AbstractFactory
{
public function createPicture($path, $name = '')
{
return new Html\Picture($path, $name);
}
public function createText($content)
{
return new Html\Text($content);
}
}

View File

@ -0,0 +1,23 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Json;
use DesignPatterns\AbstractFactory\Picture as BasePicture;
/**
* Picture is a concrete image for JSON rendering
*/
class Picture extends BasePicture
{
// some crude rendering from JSON output
public function render()
{
return json_encode(array('title' => $this->_name, 'path' => $this->_path));
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory\Json;
use DesignPatterns\AbstractFactory\Text as BaseText;
/**
* Text is a text component with a JSON rendering
*/
class Text extends BaseText
{
public function render()
{
return json_encode(array('content' => $this->_text));
}
}

View File

@ -0,0 +1,26 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\AbstractFactory;
/**
* JsonFactory is a factory for creating a family of JSON component
* (example for ajax)
*/
class JsonFactory extends AbstractFactory
{
public function createPicture($path, $name = '')
{
return new Json\Picture($path, $name);
}
public function createText($content)
{
return new Json\Text($content);
}
}

View File

@ -2,7 +2,12 @@
namespace DesignPatterns\AbstractFactory;
/**
* This contract is not part of the pattern, in general case, each component
* are not related
*/
interface Media
{
function render();
}

View File

@ -2,7 +2,7 @@
namespace DesignPatterns\AbstractFactory;
class Picture implements Media
abstract class Picture implements Media
{
protected $_path;
protected $_name;

View File

@ -2,7 +2,7 @@
namespace DesignPatterns\AbstractFactory;
class Text implements Media
abstract class Text implements Media
{
/**
*

20
FactoryMethod/Bicycle.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Bicycle is a bicycle
*/
class Bicycle implements Vehicle
{
public function setColor($rgb)
{
}
}

View File

@ -1,49 +1,54 @@
<?php
namespace DesignPatterns;
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Factory Method pattern
*
* Purpose:
* similar to the AbstractFactory, this pattern is used to create series of related or dependant objects.
* The difference between this and the abstract factory pattern is that the factory method pattern uses just one static
* method to create all types of objects it can create. It is usually named "factory" or "build".
*
* Examples:
* - Zend Framework: Zend_Cache_Backend or _Frontend use a factory method create cache backends or frontends
* FactoryMethod is a factory method. The good point over the SimpleFactory
* is you can subclass it to implement different way to create vehicle for
* each country (see subclasses)
*
* For simple case, this abstract class could be just an interface
*
* This pattern is a "real" Design Pattern because it achieves the
* "Dependency Inversion Principle" a.k.a the "D" in S.O.L.I.D principles.
*
* It means the FactoryMethod class depends on abstractions not concrete classes.
* This is the real trick compared to SImpleFactory or StaticFactory.
*/
class FactoryMethod
abstract class FactoryMethod
{
const CHEAP = 1;
const FAST = 2;
/**
* the parametrized function to get create an instance
*
* @static
* @return Formatter
* The children of the class must implement this method
*
* Sometimes this method can be public to get "raw" object
*
* @param string $type a generic type
*
* @return Vehicle a new vehicle
*/
public static function factory($type)
abstract protected function createVehicle($type);
/**
* Creates a new vehicle
*
* @param int $type
*
* @return Vehicle a new vehicle
*/
public function create($type)
{
$className = 'Format' . ucfirst($type);
if ( ! class_exists($className)) {
throw new Exception('Missing format class.');
}
$obj = $this->createVehicle($type);
$obj->setColor("#f00");
return new $className();
return $obj;
}
}
interface Formatter
{
}
class FormatString implements Formatter
{
}
class FormatNumber implements Formatter
{
}
}

20
FactoryMethod/Ferrari.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Ferrari is a italian car
*/
class Ferrari implements Vehicle
{
public function setColor($rgb)
{
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* GermanFactory is vehicle factory in Germany
*/
class GermanFactory extends FactoryMethod
{
/**
* @inheritdoc
*/
protected function createVehicle($type)
{
switch ($type) {
case parent::CHEAP :
return new Bicycle();
break;
case parent::FAST :
$obj = new Porsche();
// we can specialize the way we want some concrete Vehicle since
// we know the class
$obj->addTuningAMG();
return $obj;
break;
default :
throw new \InvalidArgumentException("$type is not a valid vehicle");
}
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* ItalianFactory is vehicle factory in Italy
*/
class ItalianFactory extends FactoryMethod
{
/**
* @inheritdoc
*/
protected function createVehicle($type)
{
switch ($type) {
case parent::CHEAP :
return new Bicycle();
break;
case parent::FAST :
return new Ferrari();
break;
default :
throw new \InvalidArgumentException("$type is not a valid vehicle");
}
}
}

25
FactoryMethod/Porsche.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Porsche is a german car
*/
class Porsche implements Vehicle
{
public function setColor($rgb)
{
}
public function addTuningAMG()
{
}
}

16
FactoryMethod/Vehicle.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\FactoryMethod;
/**
* Vehicle is a contract for a vehicle
*/
interface Vehicle
{
function setColor($rgb);
}

20
SimpleFactory/Bicycle.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* Bicycle is a bicycle
*/
class Bicycle implements Vehicle
{
public function driveTo($destination)
{
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* ConcreteFactory is a simple factory pattern.
*
* It differs from the static factory because it is NOT static and as you
* know : static => global => evil
*
* Therefore, you can haZ multiple factories, differently parametrized,
* you can subclass it and you can mock-up it.
*/
class ConcreteFactory
{
protected $typeList;
/**
* You can imagine to inject your own type list or merge with
* the default ones...
*/
public function __construct()
{
$this->typeList = array(
'bicycle' => __NAMESPACE__ . '\Bicycle',
'other' => __NAMESPACE__ . '\Scooter'
);
}
/**
* Creates a vehicle
*
* @param string $type a known type key
* @return Vehicle a new instance of Vehicle
* @throws \InvalidArgumentException
*/
public function createVehicle($type)
{
if (!array_key_exists($type, $this->typeList)) {
throw new \InvalidArgumentException("$type is not valid vehicle");
}
$className = $this->typeList[$type];
return new $className();
}
}

20
SimpleFactory/Scooter.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* Scooter is a Scooter
*/
class Scooter implements Vehicle
{
public function driveTo($destination)
{
}
}

16
SimpleFactory/Vehicle.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\SimpleFactory;
/**
* Vehicle is a contract for a vehicle
*/
interface Vehicle
{
function driveTo($destination);
}

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\StaticFactory;
class FormatNumber implements Formatter
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\StaticFactory;
class FormatString implements Formatter
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace DesignPatterns\StaticFactory;
interface Formatter
{
}

View File

@ -0,0 +1,39 @@
<?php
namespace DesignPatterns\StaticFactory;
/**
* Static Factory pattern
*
* Purpose:
* similar to the AbstractFactory, this pattern is used to create series of related or dependant objects.
* The difference between this and the abstract factory pattern is that the static factory pattern uses just one static
* method to create all types of objects it can create. It is usually named "factory" or "build".
*
* Examples:
* - Zend Framework: Zend_Cache_Backend or _Frontend use a factory method create cache backends or frontends
*
* Note1: Remember, static => global => evil
* Note2: Cannot be subclassed or mock-uped or have multiple different instances
*/
class StaticFactory
{
/**
* the parametrized function to get create an instance
*
* @static
* @return Formatter
*/
public static function factory($type)
{
$className = __NAMESPACE__ . '\Format' . ucfirst($type);
if (!class_exists($className)) {
throw new \InvalidArgumentException('Missing format class.');
}
return new $className();
}
}

View File

@ -0,0 +1,50 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\AbstractFactory;
use DesignPatterns\AbstractFactory\AbstractFactory;
use DesignPatterns\AbstractFactory\HtmlFactory;
use DesignPatterns\AbstractFactory\JsonFactory;
/**
* AbstractFactoryTest tests concrete factories
*/
class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
{
public function getFactories()
{
return array(
array(new JsonFactory()),
array(new HtmlFactory())
);
}
/**
* This is the client of factories. Note that the client does not
* care which factory is given to him, he can create any component he
* wants and render how he wants.
*
* @dataProvider getFactories
*/
public function testComponentCreation(AbstractFactory $factory)
{
$article = array(
$factory->createText('Lorem Ipsum'),
$factory->createPicture('/image.jpg', 'caption'),
$factory->createText('footnotes')
);
$this->assertContainsOnly('DesignPatterns\AbstractFactory\Media', $article);
/* this is the time to look at the Builder pattern. This pattern
* helps you to create complex object like that article above with
* a given Abstract Factory
*/
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\FactoryMethod;
use DesignPatterns\FactoryMethod\FactoryMethod;
use DesignPatterns\FactoryMethod\GermanFactory;
use DesignPatterns\FactoryMethod\ItalianFactory;
/**
* FactoryMethodTest tests the factory method pattern
*/
class FactoryMethodTest extends \PHPUnit_Framework_TestCase
{
protected $type = array(
FactoryMethod::CHEAP,
FactoryMethod::FAST
);
public function getShop()
{
return array(
array(new GermanFactory()),
array(new ItalianFactory())
);
}
/**
* @dataProvider getShop
*/
public function testCreation(FactoryMethod $shop)
{
// this test method acts as a client for the factory. We don't care
// about the factory, all we know is it can produce vehicle
foreach ($this->type as $oneType) {
$vehicle = $shop->create($oneType);
$this->assertInstanceOf('DesignPatterns\FactoryMethod\Vehicle', $vehicle);
}
}
/**
* @dataProvider getShop
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage spaceship is not a valid vehicle
*/
public function testUnknownType(FactoryMethod $shop)
{
$shop->create('spaceship');
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
* DesignPatternPHP
*/
namespace DesignPatterns\Tests\SimpleFactory;
use DesignPatterns\SimpleFactory\ConcreteFactory;
/**
* SimpleFactoryTest tests the Simple Factory pattern
*
* @author flo
*/
class SimpleFactoryTest extends \PHPUnit_Framework_TestCase
{
protected $factory;
protected function setUp()
{
$this->factory = new ConcreteFactory();
}
public function getType()
{
return array(
array('bicycle'),
array('other')
);
}
/**
* @dataProvider getType
*/
public function testCreation($type)
{
$obj = $this->factory->createVehicle($type);
$this->assertInstanceOf('DesignPatterns\SimpleFactory\Vehicle', $obj);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testBadType()
{
$this->factory->createVehicle('car');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace DesignPatterns\Tests\StaticFactory;
use DesignPatterns\StaticFactory\StaticFactory;
/**
* Tests for Static Factory pattern
*
*/
class StaticFactoryTest extends \PHPUnit_Framework_TestCase
{
public function getTypeList()
{
return array(
array('string'),
array('number')
);
}
/**
* @dataProvider getTypeList
*/
public function testCreation($type)
{
$obj = StaticFactory::factory($type);
$this->assertInstanceOf('DesignPatterns\StaticFactory\Formatter', $obj);
}
}