PHP 7でFizzBuzz

  • 21
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

みんな、あたらしい言語を覚えたらFizzBuzzを書きたがりますよね。

私も最終鬼畜FizzBuzz大全とかイキがってた時代がありました。現在はまさに約束された誰だって婆だってFizzBuzzが書ける2016年ですが、私は未だに逆FizzBuzzの解法に自信が持てません。

さて、新時代の言語と言へばPHP 7です。未だにオブジェクト指向がしっくりこないstaticおにいさんなのですが、新しい言語で初めてFizzBuzzを書くともなれば張りきってもしまひます。仕事ではがんばってPHP 7導入の準備を進めてるものの、まだ5系に留まってるので、うづうづしてる気持ちもあります。

そんなこともあって、どうせならエンタープライズに耐へうる品質で書きたい!

でもデザインパターンがすごく苦手でおぼえられないので、デザインパターン | TECHSCORE(テックスコア)にお世話になってにらめっこしながら、それっぽいクラス名とかメソッド名をつけていくことにします。英語とデザインパターンは本とか読んでも全然覚えられないんです。「英語と恋はうまくならない」ってモモーイも言ってた。

最初に呼び出し側のコードから。take()は自作の補助函数です。これは配列とかforeachでループ可能な何かの先頭$n要素を取り出してくるだけのやつです。

<?php

use FizzBuzz;

$map = [
    3 => '\FizzBuzz\FizzValue',
    5 => '\FizzBuzz\BuzzValue',
];
$factory = new FizzBuzz\FizzBuzzFactory(new FizzBuzz\Mapper($map));

foreach (take($factory->generate(), 100) as $v) {
    echo $v, PHP_EOL;
}
exit;

/**
 * @param  array|\Traversable $iter
 * @param  int                $n
 * @return \Generator
 */
function take($iter, $n)
{
    $i = 0;
    foreach ($iter as $x) {
        if ($n <= $i++) { return; }
        yield $x;
    }
}

FizzBuzz\FizzBuzzFactoryFizzBuzzStrategyに従ってFizzBuzzしてくれるやつです。StrategyパターンAbstractFactoryパターンの合せ技(キリッ のつもりですが、まちがってるかもしれません。FizzBuzz\MapperFizzBuzz\FizzBuzzStrategyを実装したクラスです。

FizzBuzz\Mapperには連想配列を渡して、「3の倍数なら\FizzBuzz\FizzValue」「5の倍数なら\FizzBuzz\BuzzValue」と、動的に対応するクラス名の文字列を指定してやることができる仕組みです。

そうそう、エンタープライズの世界なので、ただの文字列じゃなくて値を表すオブジェクトを対象にすることにします。ここではabstract FizzBuzz\Valueって抽象クラスがそれです。それを継承したクラスとしてFizzValueBuzzValueNumberValueを用意しました。これらはそれぞれ__toString()メソッドを持ち、"Fizz", "Buzz"、そして数字を十進数表現の文字列(たとへば"11")に文字列化することができます。

おやおやFizzとBuzzはわかったよ、じゃあ"FizzBuzz"はどうやって出すんだね。みなさまご存じの通り、「3の倍数」であることと「5の倍数」であることは同時に成り立ちます。そして今回は縛りとして、abstractなクラス以外は継承できないようにfinal classにしてあります。そもそもPHPに多重継承はないし… そんなときのためのComposableValueクラスです。こいつがあれば、FuzzBuzz\Valueを実装したクラスがいくつあろうと、状態を組み合せられます。やったぞ!

そろそろ説明おにいさんも飽きてきたのでコード見せます。

<?php

namespace FizzBuzz
{
    // Strategy

    interface FizzBuzzStrategy
    {
        public function fizzbuzzize(int $n) :Value;
    }

    final class Mapper implements FizzBuzzStrategy
    {
        /** @var array */
        private $fizzbuzz_map;

        /**
         * @param array $fizzbuzz_map
         */
        public function __construct(array $fizzbuzz_map)
        {
            $this->fizzbuzz_map = $fizzbuzz_map;
        }

        /**
         * @param  int $n
         * @return Value
         */
        public function fizzbuzzize(int $n) :Value
        {
            $vs = [];

            foreach ($this->fizzbuzz_map as $m => $v) {
                if ($n % $m == 0) {
                    $vs[] = ($v instanceof \Closure) ? $v($n) : new $v($n);
                }
            }

            return (count($vs) > 0) ? new ComposableValue($n, ...$vs) : new NumberValue($n);
        }
    }

    // Value

    abstract class Value
    {
        /** @var int */
        protected $n;

        public function __construct(int $n)
        {
            $this->n = $n;
        }

        abstract function __toString();
    }

    final class NumberValue extends Value
    {
        public function __toString()
        {
            return sprintf("%d", $this->n);
        }
    }

    trait SymbolToString
    {
        public function __toString()
        {
            return static::SYMBOL;
        }
    }

    final class FizzValue extends Value
    {
        use SymbolToString;
        const SYMBOL = 'Fizz';
    }

    final class BuzzValue extends Value
    {
        use SymbolToString;
        const SYMBOL = 'Buzz';
    }

    final class ComposableValue extends Value
    {
        /** @var Value[] */
        private $values = [];

        public function __construct(int $n, Value ...$values)
        {
            parent::__construct($n);

            $this->values = array_values($values);
        }

        public function add(Value $value)
        {
            $this->values[] = $value;
        }

        public function __toString()
        {
            $s = '';

            foreach ($this->values as $v) {
                $s .= $v;
            }

            return $s;
        }
    }

    // Factory

    final class FizzBuzzFactory
    {
        /** @var FizzBuzzStrategy */
        private $strategy;

        public function __construct(FizzBuzzStrategy $strategy)
        {
            $this->strategy = $strategy;
        }

        /**
         * @param  int $from
         * @return \Generator
         */
        public function generate(int $from = 1)
        {
            $n = $from;
            while (true) {
                yield $this->strategy->fizzbuzzize($n++);
            }
        }
    }
}

(TODO: あとでリポジトリつくってGitHubに置く)

GitHubに置きました https://github.com/BaguettePHP/FizzBuzz

composer g require baguette/fizzbuzz

Composerでインストールしよう。

まとめ

だるいので、ここで使ってるPHPの言語機能を列挙するだけにしておきます ヾ(〃><)ノ゙

 

 
 

 

 


おまけ

なんでわざわざFizzBuzz\Mapperなんてクラスを分けたのかって? それに、上のコードには無名クラスなんてどこにもないじゃないか。うんうん。

それはね、頭のとんがった上司に「チミィ、このプログラムに7の倍数だったときはNassって出力する機能を追加してくれたまへよ」って言はれたときのためだよ。

$map = [
    3 => '\FizzBuzz\FizzValue',
    5 => '\FizzBuzz\BuzzValue',
    7 => function (int $n) {
        return new class($n) extends FizzBuzz\Value { use FizzBuzz\SymbolToString; const SYMBOL = 'Nass'; };
    }
];