forget for get

覚えるために忘れる

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

PHPの列挙型(Enum)

test.php

<?php
enum Suit
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

$a = Suit::Spades;
$b = Suit::Spades;

var_dump($a); // Suit::Spades
var_dump($a === $b); // true
var_dump($a instanceof Suit); // true
echo $a->name; // Spades

test2.php

<?php
enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

$c = Suit::Clubs;
echo $c->value; // C

$d = Suit::from('H');
var_dump($d); // Suit::Hearts

// $e = Suit::from('Z'); // 不正なデータはエラー
// PHP Fatal error:  Uncaught ValueError: "Z" is not a valid backing value for enum "Suit"
$e = Suit::tryFrom('Z') ?? Suit::Spades; // 不正なデータはnullが返る
var_dump($e); // Suit::Spades

var_dump(Suit::cases()); // [Suit::Hearts, Suit::Diamonds, Suit::Clubs, Suit:Spades]

 

Observerパターン

観察対象の状態が変化すると、通知する

 

Subject(被験者):NumberGenerator
 Observerを登録・削除するメソッド、現在の状態を取得するメソッドをもつ
具体的なSubject:RandomNumberGenerator
 状態が変化したらObserverに伝える
Observer(観察者):Observer
具体的なObserver:DigitObserver、GraphObserver
 updateが呼ばれると、Subjectの現在の状態を取得して何か処理をする

 

Observer.php

interface Observer {
  function update(NumberGenerator $generator): void;
}

NumberGenerator.php

abstract class NumberGenerator {
  private $observers = [];
  function addObserver(Observer $observer): void {
    $this->observers[] = $observer;
  }
  function deleteObserver(Observer $observer): void {
    foreach ($this->observers as $key => $val) {
      if ($val == $observer) unset($this->observers[$key]);
    }
  }
  function notifyObservers(): void {
    foreach ($this->observers as $observer) {
      $observer->update($this);
    }
  }
  abstract function getNumber(): int;
  abstract function execute(): void;
}

RandomNumberGenerator.php

class RandomNumberGenerator extends NumberGenerator {
  private $number;
  function getNumber(): int {
    return $this->number;
  }
  function execute(): void {
    for ($i=0;$i<3;$i++) {
      $this->number = rand(1, 20);
      $this->notifyObservers();
    }
  }
}

DigitObserver.php

class DigitObserver implements Observer {
  function update(NumberGenerator $generator): void {
    echo "DigitObserver:" . $generator->getNumber() . "\n";
    sleep(1);
  }
}

GraphObserver.php

class GraphObserver implements Observer {
  function update(NumberGenerator $generator): void {
    $str = "";
    $count = $generator->getNumber();
    for ($i=0;$i<$count;$i++) {
      $str .= "*";
    }
    echo "GraphObserver:" . $str . "\n";
    sleep(1);
  }
}

Main.php

require "../autoload.php";
$generator = new RandomNumberGenerator();
$dObserver = new DigitObserver();
$gObserver = new GraphObserver();
$generator->addObserver($dObserver);
$generator->addObserver($gObserver);
$generator->execute();

autoload.php

function autoload($className){
  require './'.$className.'.php';
}
spl_autoload_register('autoload');