26
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP 7でFizzBuzz

Last updated at Posted at 2016-08-10

みんな、あたらしい言語を覚えたら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'; };
    }
];
26
27
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?