Flyweight Pattern

同じものは一度しか作らない。

■コード

Item.php

class Item {
    private $code;
    private $name;
    private $price;
    public function __construct($code, $name, $price) {
        $this->code = $code;
        $this->name = $name;
        $this->price = $price;
    }
}

ItemFactory.php

class ItemFactory {
    private $pool;
    private static $instance = null;
    private function __construct($filename) {
        $this->buildPool($filename);
    }
    public static function getInstance($filename) {
        if(!is_null(self::$instance)) {
            self::$instance = new ItemFactory($filename);
        }
        return self::$instance;
    }
    public function getItem($code) {
        if(array_key_exists($code, $this->pool)) {
            return $this->pool($code);
        }
        else {
            return null;
        }
    }
    public function buildPool($filename) {
        $this->pool = array();
        $fh = fopen($filename, 'r');
        while($buffer = fgets($fp, 4096)) {
            list($itemCode, $itemName, $price) = split("\t", $buffer);
            $this->pool[$itemCode] = new Item($itemCode, $itemName, $price);
        }
        fclose($fh);
    }
    public final __clone() {
        throw new RuntimeException();
    }
}

■クライアントコード

$factory = ItemFactory::getInstance('list.dat');
$factory->getItem(123);// 何度コールしても同じインスタンスが返るはず
$factory->getItem(456);//
$factory->getItem(789);//

Singletonをまとめた感じだ。

Command Pattern

■コード

File.php

class File {
    private $name;
    public function __construct($name) {
        $this->name = $name;
    }
    public function decompress() {
        // 
    }
    public function compress() {
        //
    }
    public function create() {
        //
    }
}

Command.php

interface Command {
    public function execute();
}

TouchCommand.php

class TouchCommand implements Command {
    private $file;
    public function __construct(File $file) {
        $this->file = $file;
    }
    public function execute() {
        $this->file->create();
    }
}

CompressCommand.php

class CompressCommand implements Command {
    private $file;
    public function __construct(File $file) {
        $this->file = $file;
    }
    public function execute() {
        $this->file->compress();
    }
}

CopyCommand.php

class CopyCommand implements Command {
    private $file;
    public function __construct(File $file) {
        $this->file = $file;
    }
    public function execute() {
        $file = new File('copy_' . $this->file->getName());
        $file->create();
    }
}

Queue.php

class Queue {
    private $commands;
    private $currentIndex;
    public function __construct() {
        $this->commands = array();
        $this->currenrIndex = 0;
    }
    public function addCommand(Command $command) {
        $this->commands[] = $command;
    }
    public function run() {
        while(!is_null($command = $this->next())) {
            $command->execute();
        }
    }
    private function execute() {
        if(count($this->commands) === 0 || count($this->commands) <= $this->currentIndex) {
            return null;
        }
        else {
            return $this->commands[$this->currentIndex++];
        }
    }
}

■クライアントコード

$queue = new Queue();
$file = new File('hoge.txt');
$queue->addCommand(new TouchCommand($file));
$queue->addCommand(new CompressCommand($file));
$queue->addCommand(new CopyCommand($file));
$queue->run();

Chain of Responsibility

■コード

AbstractValidationHandler

abstract class AbstractValidationHandler {
    private $nextHandler;
    public function __construct() {
        $this->nextHandler = null;
    }
    public function setHandler(ValidationHandler $handler) {
        $this->nextHandler = $handler;
        return $this;
    }
    public function getNextHandler() {
        return $this->nextHandler;
    }
    public function validate($input) {
        $result = $this->execValidation($input);
        if(!$result) {
            return $this->getErrorMessage();
        }
        else if(!is_null($this->getNextHandler())) {
            return $this->getNextHandler()->validation($input);
        }
        else {
            return true;
        }
    }
    protected abstract function execValidation($input);
    protected abstract function getErrorMessage();
}

AlphaNumericValidation.php

class AlphaNumericValidation extends AbstractValidationHandler {
    protected function execValidation($input) {
        return preg_match('/^[0-9a-zA-Z]+$/', $input);
    }
    protected function getErrorMessage() {
        return '半角英数字で!';
    }
}

NumericValidation.php

class NumericValidation extends AbstractValidationHandler {
    protected function execValidation($input) {
        return preg_match('/^[0-9]+$/', $input);
    }
    protected function getErrorMessage() {
        return '半角数字で!';
    }
}

■クライアントコード

$alphaNumericValidation = new AlphaNumericValidation();
$handler = $alphaNumericValidation->setHandler(new NumericValidation());
$result = $hander->validete($input);
if($result) {
    // ok
}
else {
    // error
}

Builder Pattern

■コード

News.php

単一のnewsのデータ構造を示す。

class News {
    private $title;
    private $url;
    private $targetDate;
    public function __construct($title, $url, $targetDate) {
        $this->title = $title;
        $this->url   = $title;
        $this->targetDate = $targetDate;
    }
}

NewsDirector.php

class NewsDirector {
    private $builder;
    private $url;
    public function __construct(NewsBuilder $builder, $url) {
        $this->builder = $builder;
        $this->url = $url;
    }
    public function getNews() {
        return $this->builder->parse($this->url);
    }
}

NewsBuilder.php

interface NewsBuilder {
    public function parse($date);
}

RssNewsBuilder.php

class RssNewsBuilder implements NewsBuilder {
    public function parse($url) {
        // RSSを取得する => Newsオブジェクトのリストを返す
    }
}

■クライアントコード

$builder = new RssNewsBuilder();
$director = new NewsDirector($builder, $url);
$news = $director->getNews();
var_dump($news);

Bridge Pattern

■コード

DataSourceInterface.php

interface DataSourceInterface {
    public function open();
    public function read();
    public function close();
}

FileDataSource.php

class FileDataSource implements {
    private $sourceName;
    private $handler;
    public function __construct($sourceName) {
        $this->sourceName = $sourceName;
    }
    public function open() {
        if(is_readable($this->sourceName) && $this->handler = fopen($this->sourceName)) {
            throw new Exception();
        }
    }
    public function read() {
        $buffer = array();
        while(!feof($this->handler)) {
            $buffer[] = fgets($this->handler);
        }
        return implode($buffer);
    }
}

Listing.php

class Listing {
    private $dataSource;
    public function __construct($dataSource) {
        $this->dataSource = $dataSource;
    }
    public function open() {
        $this->dataSource->open();
    }
    public function read() {
        return $this->dataSource->read();
    }
    public function close() {
        $this->dataSource->close();
    }
}

ExtendedListing.php

class ExtendedListing extends Listing {
    public function __construct($dataSource) {
        parent::__construct($dataSource);
    }
    public function readWithEncode($charset) {
        return htmlspecialchars($this->read, ENT_QUOTE, $charset);
    }
}

■クライアントコード

$list1 = new Listing(new FileDataSource('/home/hoge/fuga.txt'));
$list2 = new ExtendedListing(new FileDataSource('/home/hoge/piyo.txt'));
try {
    $list1->open();
    $list2->open();
    echo $list1->read();
    echo $list2->read();
    $list1->close();
    $list2->close();
}
catch (Exception $e) {

}

Abstract Factory Pattern

■コード

DaoFactory.php

interface DaoFactory {
    public function createItemDao();
    public function createOrderDao();
}

DbFactory.php

class DbFactory implements DaoFactory {
    public function createItemDao() {
        return new DbItemDao();
    }
    public function createOrderDao() {
        return new DbOrderDao($this->createItemDao());
    }
}

MockFactory.php

class MockFactory implements DaoFactory {
    public function createItemDao() {
        return new MockItemDao();
    }
    public function createOrderDao() {
        return new MockOrderDao();
    }
}

ItemDao.php

interface ItemDao {
    public function findById($itemId);
}

OrderDao.php

interface OrderDao {
    public function findById($orderId);
}

DbItemDao.php

class DbItemDao implements ItemDao {
    public function findById($itemId) {
        // DBから$itemIdのものを取得して返す
    }
}
MockItemDao.php
class MockItemDao implements Item Dao {
    public function findById($itemId) {
        return new Item(7, "dummy");
    }
}

DbOrderDao.php

class DbOrderDao {
    public function findById($orderId) {
        // DBから$orderIdのものを取得して返す
    }
}
MockOrderDao.php
class MockOrderDao implements OrderDao {
    public function findById($orderId) {
        $order = new Order(123);
        $order->addItem(new Item(7, "dummy1"))->addItem(new Item(11, "dummy2"))->addItem(new Item(13, "dummy3"));
        return $order;
    }
}

Item.php

class Item {
    private $id;
    private $name;
    public function __construct($id, $name) {
        $this->id   = $id;
        $this->name = $name;
    }
}

Order.php

class Order {
    private $id;
    private $items;
    public function __construct($id) {
        $this->id = $id;
    }
    public function addItem(Item $item) {
        $id = $item->getId();
        if(!array_key_exists($id, $this->items)) {
            $this->items[$id]['object'] = $item;
            $this->items[$id]['amount'] = $item;
        }
        $this->items[$id]['amount']++;
        return $this;
    }
}

■クライアントコード

define('IS_DEVELOP', true);
if(IS_DEVELOP) {
    $factory = new MockFactory();
}
else {
    $factory = new DbFactory();
}
$itemDao   = $factory->createItemDao();
$orederDao = $factory->createOrderDao();

// 処理
$itemDao->findById(23);
$orderDao->findById(29);

Template Methodパターン

abstract class AbstractDisplay {
    private $data;
    public function __construct($data) {
        if(!is_array($data)) {
            $data = array($data);
        }
        $this->data = $data;
    }
    public function display() {
        $this->displayHeader();
        $this->displayBody();
        $this->displayFooter();
    }
    public function getData() {
        return $this->data;
    }
    protected abstract function displayHeader();
    protected abstract function displayBody();
    protected abstract function displayFooter();
}
class ListDisplay extends AbstractDisplay {
    protected function displayHeader(){
        echo '<dl>';
    }
    protected function displayBody() {
        foreach($this->getData() as $key => $value){
            echo "<dt>{$key}</dt>¥n<dd>{$value}</dd>";
        }
    }
    protected function displayFooter() {
        echo '</dl>'
    }
}
class TableDisplay extends AbstractDisplay {
    protected function displayHeader(){
        echo '<table>';
    }
    protected function displayBody() {
        foreach($this->getData() as $key => $value){
            echo "<tr><th>{$key}</th><td>{$value}</td></tr>";
        }
    }
    protected function displayFooter() {
        echo '</table>'
    }
}

PHPでデザインパターン:Iterator(イテレータ)

■コード

Student.php

class Student
{
    private $name;
    private $age;
    private $gender;
    public function __construct($name, $age, $gender)
    {
        $this->name   = $name;
        $this->age    = $age;
        $this->gender = $gender;
    }
    public function getName()
    {
        return $this->name;
    }
    public function getAge()
    {
        return $this->age;
    }
    public function getGender()
    {
        return $this->gender;
    }
}

Students.php

class Students implements IteratorAggregate
{
    private $students;
    public function __construct()
    {
        $this->students = new ArrayObject();// ArrayObjectで配列オブジェクトとして内部で保持
    }
    public function add(Student $student)
    {
        $this->students[] = $student;
    }
    public function getIterator()
    {
        return $this->students->getIterator();// 配列オブジェクトのイテレータ
    }
}

MaleIterator.php

FilterIteratorを用いることで以下のようにiteratorに付加的な機能を提供できる。

require_once 'Student.php';

class MaleIterator extends FilterIterator
{
    public function __construct($iterator)
    {
        parent::__construct($iterator);
    }
    public function accept()
    {
        $student = $this->current();
        return ($student->getGender() === 'male');
    }
}

■クライアントコード

require_once 'Student.php';
require_once 'Students.php';
require_once 'MaleIterator.php';

$students = new Students();
$students->add(new Student('John', 12, 'male'));
$students->add(new Student('Jack', 13, 'male'));
$students->add(new Student('Emily', 14, 'female'));

$iterator = $students->getIterator();
while($iterator->valid()){
    $student = $iterator->current();
    $list[] = array(
        'name'   => $student->getName(),
        'age'    => $student->getAge(),
        'gender' => $student->getGender()
    );
    $student = $iterator->next();
}
var_dump($list);
/*
array(3) {
  [0]=>
  array(3) {
    ["name"]=>
    string(4) "John"
    ["age"]=>
    int(12)
    ["gender"]=>
    string(4) "male"
  }
  [1]=>
  array(3) {
    ["name"]=>
    string(4) "Jack"
    ["age"]=>
    int(13)
    ["gender"]=>
    string(4) "male"
  }
  [2]=>
  array(3) {
    ["name"]=>
    string(5) "Emily"
    ["age"]=>
    int(14)
    ["gender"]=>
    string(6) "female"
  }
}
*/
$iterator = new MaleIterator($iterator);
foreach($iterator as $student){
    $list[] = array(
        'name'   => $student->getName(),
        'age'    => $student->getAge(),
        'gender' => $student->getGender()
    );
}
var_dump($list);
/*
array(2) {
  [0]=>
  array(3) {
    ["name"]=>
    string(4) "John"
    ["age"]=>
    int(12)
    ["gender"]=>
    string(4) "male"
  }
  [1]=>
  array(3) {
    ["name"]=>
    string(4) "Jack"
    ["age"]=>
    int(13)
    ["gender"]=>
    string(4) "male"
  }
}
*/

きれいだ。

PHPでデザインパターン:Builder(ビルダー)

Item.class.php

<?php
class Item
{
    private $_title;
    private $_url;
    private $_date;
    public function __construct($title, $url, $date)
    {
        $this->_title = $title;
        $this->_url  = $url;
        $this->_date = $date;
    }
    public function getTitle()
    {
        return $this->_title;
    }
    public function getUrl()
    {
        return $this->_url;
    }
    public function getDate()
    {
        return $this->_date;
    }
}

NewsDirector.class.php

<?php
require_once 'class/NewsDirector.class.php';

class NewsDirector
{
    private $_builder;
    private $_url;
    public function __construct(NewsBuilder $builder, $url)
    {
        $this->_builder = $builder;
        $this->_url     = $url;
    }
    public function getNews()
    {
        $list = $this->_builder->parse($this->_url)
        return $list
    }
}

NewsBuilder.class.php

<?php
interface NewsBuilder
{
    public function parse($data);
}

FeedNewsBuilder.class.php

<?php
require_once 'Item.class.php';
require_once 'NewsBuilder.class.php';

class FeedNewsBuilder implements NewsBuilder
{
    public function parse($url)
    {
        $data = new SimpleXmlElement($url, null, true);
        if($data){
            foreach($data->item as $item){
                $dc = $item->children('http://purl.org/dc/elements/1.1/');
                $list[] = new News($item->title, $item->link, $dc->date);
            }
            return $list;
        }
    }
}

index.php

クライアントとやり取りするフロント部分だ。

<?php
require_once 'class/NewsDirector.class';
require_once 'class/FeedNewsBuilder.class.php';

$builder = new FeedNewsBuilder();
$url = 'http://blog.justoneplanet.info/feed/';
$director = new NewsDirector($builder, $url);
foreach($director->getNews() as $article){
    $data = array(
        'date'  => $article->getDate(),
        'url'   => $article->getUrl(),
        'title' => $article->getTitle()
    );
}

The Standard PHP Library(SPL)

■ArrayAccess

以下のインターフェイスを実装することにより、配列のようにオブジェクトへアクセスができる。但し、ビルトインインターフェイスであり、以下のコードは記述する必要はない。

<?php
interface ArrayAccess {
    function offsetSet($offset, $value);
    function offsetGet($offset);
    function offsetUnset($offset);
    function offsetExists($offset);
}
?>
offfsetSet
配列に値をセットする
offsetGet
配列から値を得る
offsetUnset
配列から値を削除する
offsetExists
配列に要素が存在するか判定する

実例

<?php
class MyArray implements ArrayAccess {
    protected $array();
    public function offsetSet($offset, $value){
        if(!is_int($offset)){
            throw new Exception();
        }
        $this->array[$offset] = $value;
    }
    public function offsetGet($offset){
        return $this->array[$offset];
    }
    public function offsetUnset($offset){
        unset($this->array[$offset]);
    }
    public function offsetExists($offset){
        return array_key_exists($this->array, $offset);
    }
}

$obj = new MyArray();
$obj[1] = 2;
$obj['a'] = 1;//throws exception
?>

■Iterator

<?php
interface Iterator {
    function current();
    function next();
    function rewind();
    function key();
    function valid();
}
?>

実例

<?php
class MyData implements Iterator {
    private $myData = array(
        "Mike",
        "Jack",
        "Emily",
        "Naomi"
    );
    private $current = 0;
    public function current(){
        return $this->myData[$this->current];
    }
    public function next(){
        $this->current++;
    }
    public function rewind(){
        $this->current = 0;
    }
    public function key(){
        return $this->current;
    }
    public function valid(){
        return isset($this->myData[$this->current]);
    }
}

$data = new MyData();
foreach($data as $key => $value){
    print("{$key} : {$value}");
}
?>

■SeekableIterator

<?php
interface SeekableIterator {
    function current();
    function next();
    function rewind();
    function key();
    function valid();
    function seek($index);
}
?>

■Recursive Iteration

<?php
RecursiveIteratorIterator   implements OuterIterator, Traversable, Iterator {
    public function current(void);
    public function getDepth(void);
    public function getSubIterator(void);
    public function key(void);
    public function next(void);
    public function rewind(void);
    public function valid(void);
}
?>

実例

<?php
class Company_Iterator extends RecursiveIteratorIterator {
    public function beginChildren(){
        if($this->getDepth() >= 3){
            print(str_repeat("t", $this->getDepth() - 1));
            print('<ul>');
        }
    }
    public function endChildren(){
        if($this->getDepth() >= 3){
            print(str_repeat("t", $this->getDepth() - 1));
            print('</ul>');
        }
    }
}
class RecursiveArrayObject extends ArrayObject {
    function getIterator(){
        return new RecursiveArrayIterator($this);
    }
}
$company = array(
    array("Acme Anvil Co."),
    array(
        array(
            "Human Resources",
            array(
                'Nick',
                'Jack',
                'Mike'
            )
        ),
        array(
            "Accounting",
            array(
                'John',
                'Emily',
                'Naomi'
            )
        )
    )
);

$it = new Company_Iterator(new RecursiveArrayObject($company));
foreach($it as $item){
    print(str_replace("t", $it->getDepth()));
    switch($it->getDepth()){
        case 1:
            print("<h1>Company: {$item}</h1>" . PHP_EOL);
            break;
        case 2:
            print("<h2>Department: {$item}</h2>" . PHP_EOL);
            break;
        default:
            print("<li>{$item}</li>" . PHP_EOL);
    }
}
/*
	<h1>Company: Acme Anvil Co.</h1>
		<h2>Department: Human Resources</h2>
		<ul>
			<li>Nick</li>
			<li>Jack</li>
			<li>Mike</li>

		</ul>
		<h2>Department: Accounting</h2>
		<ul>
			<li>John</li>
			<li>Emily</li>
			<li>Naomi</li>
		</ul>
*/
?>

■Recursive Iteration

<?php
class NumberFilter extends FilterIterator {
    const FILTER_EVEN = 1;
    const FILTER_ODD = 2;
    private $_type;
    public function __construct($iterator, $odd_or_even = self::FILTER_EVEN){
        $this->_type = $odd_or_even;
        parent::__construct($iterator);
    }
    public function accept(){
        if($this->_type === self::FILTER_EVEN){
            return ($this->current() % 2 == 0);
        }
        else{
            return ($this->current() % 2 == 1);
        }
    }
}

$numbers = new ArrayObject(range(0, 10));
$number_it = new ArrayIterator($numbers);

$it = new NumberFilter($numbers_it, NumberFilter::FILTER_ODD)

foreach($it as $number){
    print("{$number}" . PHP_EOL);
}
?>