forget for get

覚えるために忘れる

Compositeパターン

容器と中身の同一視。
ファイルシステムでは、ディレクトリとファイルが入れ子になっている。
ディレクトリとファイルは異なるものですが、「ディレクトリに入るもの」とみなしている。

Leaf(葉):MyFile
中身
Composite(複合体):MyDirectory
容器
Component:Entry
LeafとCompositeを同一視するためのスーパークラス
Client(依頼者):Main

他にも複数と単数の同一視など。

Entry.php

abstract class Entry {
  abstract function getName();
  abstract function getSize();
  function add(Entry $entry) {
    throw new Exception("FileTreatmentException");
  }
  abstract function printList($prefix=null);
  function toString() {
    return $this->getName() . "(" . $this->getSize() . ")";
  }
}

MyFile.php

class MyFile extends Entry {
    private $name;
    private $size;
    function __construct(String $name, int $size) {
        $this->name = $name;
        $this->size = $size;
    }
    function getName() {
        return $this->name;
    }
    function getSize() {
        return $this->size;
    }
    function printList($prefix=null) {
        echo $prefix . "/" . $this->toString() . "\n";
    }
}

MyDirectory.php

class MyDirectory extends Entry {
    private $name;
    private $directory = [];
    function __construct(String $name) {
        $this->name = $name;
    }
    function getName() {
        return $this->name;
    }
    function getSize() {
        $size = 0;
        foreach ($this->directory as $entry) {
            $size += $entry->getSize();
        }
        return $size;
    }
    function add(Entry $entry) {
        $this->directory[] = $entry;
        return $this;
    }
    function printList($prefix=null) {
        echo $prefix . "/" . $this->toString() . "\n";
        foreach ($this->directory as $entry) {
            $entry->printList($prefix . "/" . $this->name);
        }
    }
}

Main.php

require "../autoload.php";
echo "Making root entries...\n";
$rootdir = new MyDirectory("root");
$bindir = new MyDirectory("bin");
$tmpdir = new MyDirectory("tmp");
$usrdir = new MyDirectory("usr");
$rootdir->getName();
$rootdir->add($bindir);
$rootdir->add($tmpdir);
$rootdir->add($usrdir);
$bindir->add(new MyFile("vi", 10000));
$bindir->add(new MyFile("latex", 20000));
$rootdir->printList();

 

Mementoパターン

状態を保存しておいて、復元できるようにしておく。
undo,redo,history,snapshot

復元するためにインスタンスの内部情報を公開すると、カプセル化の破壊になるので、
インタフェースを使い分ける。

Originator(作成者):Gamer
自分の現在の状態を保存したいときにMementoをつくる。
以前のMementoを渡されると、その状態に戻る。
Memento(記念品):Memento
Originatorの情報を持っているが、誰にでも公開はしない。
広いインタフェース→オブジェクトを元に戻すために内部情報を得られるメソッド。Originatorだけが使える。
狭いインタフェース→Caretakerに見せるもの。
Caretaker(世話をする人):Main
Originatorの状態を保存したいときに、Originatorに伝える。
OriginatorはMementoをCaretakerに渡す。
CaretakerはMementoの狭いインタフェースしか使えないので、Mementoの内部情報をいじることはできない。

Memento.php

class Memento {
    private $money;
    private $fruits = [];
    function __construct($money) {
        $this->money = $money;
    }
    function getMoney() {
        return $this->money;
    }
    function addFruit($fruit) {
        $this->fruits[] = $fruit;
    }
    function getFruits() {
        return $this->fruits;
    }
}

Gamer.php

class Gamer {
    private $money;
    private $fruits = [];
    private $fruitNames = ["夕張メロン","すいか","もも","なし"];
    function __construct($money) {
        $this->money = $money;
    }
    function getMoney() {
        return $this->money;
    }
    function bet() {
        $dice = rand(1, 6);
        if ($dice == 1) {
            $this->money += 100;
            echo "所持金が増えました\n";
        } else if ($dice == 2) {
            $this->money /= 2;
            echo "所持金が半分になりました\n";
        } else if ($dice == 6) {
            $f = $this->getFruit();
            echo "フルーツ(" . $f . ")をもらいました\n";
            $this->fruits[] = $f;
        } else {
            echo "何も起こりませんでした\n";
        }
    }
    function createMemento() {
        $m = new Memento($this->money);
        foreach ($this->fruits as $f) {
            if (strpos($f, "おいしい") !== false) {
                $m->addFruit($f);
            }
        }
        return $m;
    }
    function restoreMemento(Memento $memento) {
        $this->money = $memento->getMoney();
        $this->fruits = $memento->getFruits();
    }
    function toString() {
        return "[money = " . $this->money 
        . ", fruits = " . implode(",", $this->fruits) . "]";
    }
    private function getFruit() {
        $prefix = rand(0, 1) ? "おいしい" : "";
        return $prefix . $this->fruitNames[rand(0, count($this->fruitNames) - 1)];
    }
}

Main.php

require "../autoload.php";
$gamer = new Gamer(100);
$memento = $gamer->createMemento();
for ($i=1;$i<=100;$i++) {
    echo $i . ":" . $gamer->toString() . "\n";
    $gamer->bet();
    echo "所持金:" . $gamer->getMoney() . "\n";
    if ($gamer->getMoney() > $memento->getMoney()) {
        echo " (保存)\n";
        $memento = $gamer->createMemento();
    } else if ($gamer->getMoney() < $memento->getMoney() / 2) {
        echo " (復元)\n";
        $gamer->restoreMemento($memento);
    }
    sleep(1);
}

 

 

Strategyパターン

アルゴリズムをごっそり切り替える

Strategy(戦略):Strategy
インタフェースを規定
ConcreteStrategy(具体的戦略):WinningStrategy、ProbStrategy
Context(文脈):Player
Strategyを利用。ConcreteStrategyのインスタンスを保持。

Hand.php

class Hand {
  const GUU = 0;
  const CHO = 1;
  const PAA = 2;
  static $hand = [];
  static private $name = ["グー","チョキ","パー"];
  private $handvalue;
  static function initialize() {
    self::$hand = [new Hand(self::GUU), new Hand(self::CHO), new Hand(self::PAA)];
  }
  function __construct($handvalue) {
    $this->handvalue = $handvalue;
  }
  static function getHand($handvalue) {
    return self::$hand[$handvalue];
  }
  function isStrongerThan(Hand $h) {
    return $this->fight($h) == 1;
  }
  function isWeekerThan(Hand $h) {
    return $this->fight($h) == -1;
  }
  private function fight(Hand $h) {
    if ($this == $h) return 0;
    if (($this->handvalue + 1) % 3 == $h->handvalue) return 1;
    return -1;
  }
  function toString() {
    return self::$name[$this->handvalue];
  }
}

Strategy.php

interface Strategy {
  function nextHand(): Hand;
  function study(bool $win);
}

WinningStrategy.php

// 勝ったら同じ手を出す戦略
class WinningStrategy implements Strategy {
  private $won = false;
  private $prevHand;
  function nextHand(): Hand {
    if (!$this->won) $this->prevHand = Hand::getHand(rand(0, 2));
    return $this->prevHand;
  }
  function study(bool $win) {
    $this->won = $win;
  }
}

ProbStrategy.php

// 勝敗履歴から確率を計算する戦略
class ProbStrategy implements Strategy {
  private $prevHandValue = 0;
  private $currentHandValue = 0;
  private $history = [[1,1,1], [1,1,1], [1,1,1]];
  function nextHand(): Hand {
    $bet = rand(0, $this->getSum($this->currentHandValue));
    $handvalue = 0;
    if ($bet < $this->history[$this->currentHandValue][0]) {
      $handvalue = 0;
    } else if ($bet < $this->history[$this->currentHandValue][0] + $this->history[$this->currentHandValue][1]) {
      $handvalue = 1;
    } else {
      $handvalue = 2;
    }
    $this->prevHandValue = $this->currentHandValue;
    $this->currentHandValue = $handvalue;
    return Hand::getHand($handvalue);
  }
  private function getSum($hv) {
    $sum = 0;
    for ($i=0;$i<3;$i++) {
      $sum += $this->history[$hv][$i];
    }
    return $sum;
  }
  function study($win) {
    if ($win) {
      $this->history[$this->prevHandValue][$this->currentHandValue]++;
    } else {
      $this->history[$this->prevHandValue][($this->currentHandValue + 1) % 3]++;
      $this->history[$this->prevHandValue][($this->currentHandValue + 2) % 3]++;
    }
  }
}

Player.php

class Player {
  private $name;
  private $strategy;
  private $wincount = 0;
  private $losecount = 0;
  private $gamecount = 0;
  function __construct($name, $strategy) {
    $this->name = $name;
    $this->strategy = $strategy;
  }
  function nextHand() {
    return $this->strategy->nextHand();
  }
  function win() {
    $this->strategy->study(true);
    $this->wincount++;
    $this->gamecount++;
  }
  function lose() {
    $this->strategy->study(false);
    $this->losecount++;
    $this->gamecount++;
  }
  function even() {
    $this->gamecount++;
  }
  function toString() {
    return "[" . $this->name . ":" . $this->gamecount . " games, " . $this->wincount . " win, " . $this->losecount . " lose]";
  }
}

Main.php

require "../autoload.php";
Hand::initialize();
$player1 = new Player("Taro", new WinningStrategy());
$player2 = new Player("Jiro", new ProbStrategy());
for ($i=0;$i<10;$i++) {
  $nextHand1 = $player1->nextHand();
  $nextHand2 = $player2->nextHand();
  if ($nextHand1->isStrongerThan($nextHand2)) {
    echo "Winner:" . $player1->toString() . "\n";
    $player1->win();
    $player2->lose();
  } else if ($nextHand2->isStrongerThan($nextHand1)) {
    echo "Winner:" . $player2->toString() . "\n";
    $player2->win();
    $player1->lose();
  } else {
    echo "Even...\n";
    $player1->even();
    $player2->even();
  }
}
echo "Total result:\n";
echo $player1->toString() . "\n";
echo $player2->toString() . "\n";

 

 

Bridgeパターン

機能の階層と実装の階層を分ける

機能(メソッド)を追加したいとき、サブクラス(子クラス、派生クラス、拡張クラス)をつくる
さらに機能を追加する場合、さらに階層が深くなる

Something
  SomethingGood
    SomethingBetter

抽象クラスでインタフェース(API)を規定し、サブクラスで実装する。
部品として交換しやすいクラスになる。

AbstractClass
  ConcreteClass1
  ConcreteClass2

機能の階層と実装の階層が混在していると見通しが悪いので分ける。

Abstraction(抽象化):Display
機能クラス階層の最上位。
Implementorを保持し、そのメソッドを使って機能を記述。

RefinedAbstraction(改善した抽象化):CountDisplay
Abstractionに機能を追加したもの。

Implementor(実装者):DisplayImpl
実装クラス階層の最上位。
Abstractionのインタフェースを実装するためのメソッドを規定。

ConcreteImplementor(具体的な実装者):StringDisplayImpl
Implementorを実装。

2つの階層を$implが橋渡ししている。
分けておけば拡張するのが楽になる。

継承だとソースコードを書き換えないと変わらない。
Displayのように、委譲すると、引数で渡すものを変えれば変わる。

Display.php

class Display {
  private $impl;
  function __construct(DisplayImpl $impl) {
    $this->impl = $impl;
  }
  function open() {
    $this->impl->rawOpen();
  }
  function print() {
    $this->impl->rawPrint();
  }
  function close() {
    $this->impl->rawClose();
  }
  final function display() {
    $this->open();
    $this->print();
    $this->close();
  }
}

CountDisplay.php

class CountDisplay extends Display {
  function __construct(DisplayImpl $impl) {
    parent::__construct($impl);
  }
  function multiDisplay($times) {
    $this->open();
    for ($i=0;$i<$times;$i++) {
      $this->print();
    }
    $this->close();
  }
}

DisplayImpl.php

abstract class DisplayImpl {
  abstract function rawOpen();
  abstract function rawPrint();
  abstract function rawClose();
}

StringDisplayImpl.php

class StringDisplayImpl extends DisplayImpl {
  private $string;
  private $width;
  function __construct($string) {
    $this->string = $string;
    $this->width = strlen($string);
  }
  function rawOpen() {
    $this->printLine();
  }
  function rawPrint() {
    echo "|" . $this->string . "|\n";
  }
  function rawClose() {
    $this->printLine();
  }
  private function printLine() {
    echo "+";
    for ($i=0;$i<$this->width;$i++) {
      echo "-";
    }
    echo "+\n";
  }
}

Main.php

require "../autoload.php";
$d1 = new Display(new StringDisplayImpl("Hello"));
$d2 = new CountDisplay(new StringDisplayImpl("Good bye"));
$d3 = new CountDisplay(new StringDisplayImpl("Good night"));
$d1->display();
$d2->display();
$d3->display();
$d3->multiDisplay(3);

 

Abstract Factoryパターン

抽象的な工場では、抽象的な部品を組み合わせて抽象的な製品をつくる
具体的な実装には注目せず、インタフェースだけを使って、部品を組み立て、製品にまとめる

具体的な工場を新たに追加するのは簡単
部品を新たに追加するのは困難(すべての具体的な工場に追加する必要があるので)

 

Factory.php

abstract class Factory {
  static function getFactory($classname): Factory {
    try {
      return new $classname();
    } catch (Exception $e) {
      echo $e->getMessage() . "\n";
    }
  }
  abstract function createLink($caption, $url): Link;
  abstract function createTray($caption): Tray;
  abstract function createPage($title, $author): Page;
}

Item.php

abstract class Item {
  protected $caption;
  function __construct($caption) {
    $this->caption = $caption;
  }
  abstract function makeHTML();
}

Link.php

abstract class Link extends Item {
  protected $url;
  function __construct($caption, $url) {
    parent::__construct($caption);
    $this->url = $url;
  }
}

Tray.php

abstract class Tray extends Item {
  protected $tray = [];
  function __construct($caption) {
    parent::__construct($caption);
  }
  function add(Item $item) {
    $this->tray[] = $item;
  }
}

Page.php

abstract class Page {
  protected $title;
  protected $author;
  protected $content = [];
  function __construct($title, $author) {
    $this->title = $title;
    $this->author = $author;
  }
  function add(Item $item) {
    $this->content[] = $item;
  }
  function output() {
    try {
      $filename = $this->title . ".html";
      file_put_contents($filename, $this->makeHTML());
      echo $filename . "を作成しました。\n";
    } catch (Exception $e) {
      echo $e->getMessage() . "\n";
    }
  }
  abstract function makeHTML();
}

Main.php

require "../autoload.php";
if (!isset($argv[1])) {
  echo "引数を指定してください\nphp Main.php [ListFactory/TableFactory]\n";
  return;
}
$factory = Factory::getFactory($argv[1]);
$twitter = $factory->createLink("twitter", "https://twitter.com/lightwill0309");
$youtube = $factory->createLink("youtube", "https://www.youtube.com/@lightwill");
$guitarflix = $factory->createLink("ギターフリックス", "https://lightwill.tokyo/guitarflix/");
$dramamegra = $factory->createLink("ドラマメグラ", "https://dramamegra.com/");

$traySns = $factory->createTray("SNS");
$traySns->add($twitter);
$traySns->add($youtube);

$trayWeb = $factory->createTray("Webサービス");
$trayWeb->add($guitarflix);
$trayWeb->add($dramamegra);

$page = $factory->createPage("LinkPage", "lightwill");
$page->add($traySns);
$page->add($trayWeb);
$page->output();

ListFactory.php

class ListFactory extends Factory {
  function createLink($caption, $url): Link {
    return new ListLink($caption, $url);
  }
  function createTray($caption): Tray {
    return new ListTray($caption);
  }
  function createPage($title, $author): Page {
    return new ListPage($title, $author);
  }
}

ListLink.php

class ListLink extends Link {
  function __construct($caption, $url) {
    parent::__construct($caption, $url);
  }
  function makeHTML() {
    return '  <li><a href="' . $this->url . '">' . $this->caption . "</a></li>\n";
  }
}

ListTray.php

class ListTray extends Tray {
  function __construct($caption) {
    parent::__construct($caption);
  }
  function makeHTML() {
    $html = "<li>\n" . $this->caption . "\n<ul>\n";
    foreach ($this->tray as $item) {
      $html .= $item->makeHTML();
    }
    $html .= "</ul>\n</li>\n";
    return $html;
  }
}

ListPage.php

class ListPage extends Page {
  function __construct($title, $author) {
    parent::__construct($title, $author);
  }
  function makeHTML() {
    $html = "<html><head><title>" . $this->title . "</title></head>\n"
      . "<body>\n<h1>" . $this->title . "</h1>\n<ul>\n";
    foreach ($this->content as $item) {
      $html .= $item->makeHTML();
    }
    $html .= "</ul>\n<hr><address>" . $this->author . "</address></body></html>\n";
    return $html;
  }
}

TableFactory.php

class TableFactory extends Factory {
  function createLink($caption, $url): Link {
    return new TableLink($caption, $url);
  }
  function createTray($caption): Tray {
    return new TableTray($caption);
  }
  function createPage($title, $author): Page {
    return new TablePage($title, $author);
  }
}

TableLink.php

class TableLink extends Link {
  function __construct($caption, $url) {
    parent::__construct($caption, $url);
  }
  function makeHTML() {
    return '  <td><a href="' . $this->url . '">' . $this->caption . "</a></td>\n";
  }
}

TableTray.php

class TableTray extends Tray {
  function __construct($caption) {
    parent::__construct($caption);
  }
  function makeHTML() {
    $html = "<td><table><tr><td>" . $this->caption . "</td></tr>\n<tr>\n";
    foreach ($this->tray as $item) {
      $html .= $item->makeHTML();
    }
    $html .= "</tr></table></td>";
    return $html;
  }
}

TablePage.php

class TablePage extends Page {
  function __construct($title, $author) {
    parent::__construct($title, $author);
  }
  function makeHTML() {
    $html = "<html><head><title>" . $this->title . "</title></head>\n"
      . "<body>\n<h1>" . $this->title . "</h1>\n<table>\n";
    foreach ($this->content as $item) {
      $html .= "<tr>" . $item->makeHTML() . "</tr>";
    }
    $html .= "</table>\n<hr><address>" . $this->author . "</address></body></html>\n";
    return $html;
  }
}

 

Template Methodパターン

スーパークラスで抽象メソッドの呼び出しの流れを定め、サブクラスで具体的な処理を実装
ロジックが共通化できる

 

AbstractDisplay.php

abstract class AbstractDisplay {
  abstract function open();
  abstract function print();
  abstract function close();
  function display() {
    $this->open();
    $this->print();
    $this->close();
  }
}

CharDisplay.php

class CharDisplay extends AbstractDisplay {
  private $ch;
  function __construct($ch) {
    $this->ch = $ch;
  }
  function open() {
    echo "<<";
  }
  function print() {
    echo $this->ch;
  }
  function close() {
    echo ">>\n";
  }
}

StringDisplay.php

class StringDisplay extends AbstractDisplay {
  private $string;
  private $width;
  function __construct($string) {
    $this->string = $string;
    $this->width = strlen($string);
  }
  function open() {
    $this->printLine();
  }
  function print() {
    echo "|" . $this->string . "|\n";
  }
  function close() {
    $this->printLine();
  }
  private function printLine() {
    echo "+";
    for ($i=0;$i<$this->width;$i++) {
      echo "-";
    }
    echo "+\n";
  }
}

Main.php

require "../autoload.php";
$d1 = new CharDisplay("H");
$d2 = new StringDisplay("Hello");
$d1->display();
$d2->display();

 

値オブジェクトの配列から値の配列を取り出す

クリーンアーキテクチャだと$itemIdがintではなく、
ItemIdクラスみたいな値オブジェクト(ValueObject)になっている。

例えば、検索クエリに指定したいときはこんな感じに書く。

Item::where('item_id', $itemId->itemId)->get();

では、$itemIdListのときは、、

$itemIdValueList = [];
foreach ($itemIdList as $itemId) {
  $itemIdValueList[] = $itemId->itemId;
}
Item::whereIn('item_id', $itemIdValueList)->get();

こんな感じで書いていた。

もっと簡潔に書ける。

$itemIdValueList = array_column($itemIdList, 'itemId');
Item::whereIn('item_id', $itemIdValueList)->get();

ちなみにEnumだとこう。

$itemTypeValueList = array_column($itemTypeList, 'value');