毎週土曜夜はワンオペ育児をしている岩原です。こんにちは。
皆さん、FizzBuzz問題
って解いたことありますか?
Fizz Buzz - Wikipedia
今回は、そのFizzBuzz問題をカルテット的考え方に基づいて解いてみようと思います。
まずは単純に
何も考えずに書くとこうなるかと思います。
<?php
function fizzBuzz(int $val) : string {
if ($val % 3 === 0 && $val % 5 === 0){
return "FizzBuzz";
}elseif ($val % 3 === 0) {
return "Fizz";
}elseif ($val % 5 === 0){
return "Buzz";
}else{
return "$val";
}
}
for ($i=1; $i <= 100; $i++) {
$ret = fizzBuzz($i);
echo $ret . ' ';
if ($i % 10 == 0) {
echo "\n";
}
}
バリエーションを考える
PHPerKaigiで「設計力を上げる!バリエーションの見極め術」を発表しました | QUARTETCOM TECH BLOGということで、まずはバリエーションの見極めをします。
と言っても、今回のバリエーションは以下の部分しかありませんね。
if ($val % 3 === 0 && $val % 5 === 0){
return "FizzBuzz";
}elseif ($val % 3 === 0) {
return "Fizz";
}elseif ($val % 5 === 0){
return "Buzz";
}else{
return "$val";
}
ここのif-elseは今後も増えていきそうですね。
その点に注意してカルテット流にするとこんな感じになります。
<?php
interface SpeakerInterface{
public function say(int $val) : string;
public function supports(int $val) : bool;
}
class FizzSpeaker implements SpeakerInterface{
public function say(int $val) : string{
return "Fizz";
}
public function supports(int $val): bool
{
return $val % 3 === 0;
}
}
class BuzzSpeaker implements SpeakerInterface{
public function say(int $val) : string{
return "Buzz";
}
public function supports(int $val): bool
{
return $val % 5 === 0;
}
}
class FizzBuzzSpeaker implements SpeakerInterface{
public function say(int $val) : string{
return "FizzBuzz";
}
public function supports(int $val): bool
{
return $val % 3 === 0 && $val % 5 === 0;
}
}
class NumberSpeaker implements SpeakerInterface{
public function say(int $val) : string{
return "$val";
}
public function supports(int $val): bool
{
return true;
}
}
class SpeakerResolver{
private $speakerList = [];
public function add(SpeakerInterface $speaker)
{
$this->speakerList[] = $speaker;
}
public function resolve(int $val) : SpeakerInterface
{
foreach ($this->speakerList as $speaker) {
if($speaker->supports($val)){
return $speaker;
}
}
throw new \Exception("対応していない値です!");
}
}
function fizzBuzz(SpeakerResolver $resolver, int $val) : string {
$speaker = $resolver->resolve($val);
return $speaker->say($val);
}
$resolver = new SpeakerResolver();
$resolver->add(new FizzBuzzSpeaker());
$resolver->add(new FizzSpeaker());
$resolver->add(new BuzzSpeaker());
$resolver->add(new NumberSpeaker());
for ($i=1; $i <= 100; $i++) {
$ret = fizzBuzz($resolver, $i);
echo $ret . ' ';
if ($i % 10 == 0) {
echo "\n";
}
}
単なるFizzBuzzになんて長さだ!と思うかもしれません。自分でもそう思います。
では1つずつ解説してきます。
解説
SpeakerInterface
今回の肝となるInterfaceです。
弊社ではオブジェクト指向の中でもとりわけポリモーフィズムを重視しています。
SpeakerInterfaceにsayメソッドとsupportsメソッドを定義し、
それぞれ「何を話すのか」と「どんな条件なのか」を実装させます。
FizzSpeaker、BuzzSpeaker、FizzBuzzSpeaker、NumberSpeaker
SpeakerInterfaceを実装したクラスになります。
それぞれ「何を話すのか」と「どんな条件なのか」を実装させます。
を実際に実装したクラスになります。 SpeakerInterfaceを実装したクラスを増やしていくことで、バリエーションの増加への対応が楽になります。
SpeakerResolver
各Speakerたちをまとめて、どのSpeakerクラスのsayメソッドを呼び出せばよいかを管理するクラスになります。
addされた順に判定していきます。
弊社ではよく使うパターンだったりします。
PhpStormでコードテンプレートを使って*Resolverクラスを楽に作る | QUARTETCOM TECH BLOG
実際にバリエーションを増やしてみる
バリエーションとして、「7の倍数の場合はHoge!
と出力する」を追加してみます。
差分はこちら。
}
}
+class HogeSpeaker implements SpeakerInterface{
+ public function say(int $val) : string{
+ return "Hoge!";
+ }
+
+ public function supports(int $val): bool
+ {
+ return $val % 7 === 0;
+ }
+}
+
class SpeakerResolver{
private $speakerList = [];
$resolver->add(new FizzBuzzSpeaker());
$resolver->add(new FizzSpeaker());
$resolver->add(new BuzzSpeaker());
+$resolver->add(new HogeSpeaker());
$resolver->add(new NumberSpeaker());
for ($i=0; $i < 100; $i++) {
こんな感じになります。 変更差分は大きく感じますが、非常にわかりやすい差分になってるかと思います。
ちなみに、単純実装の場合はこんな感じですね。 なんとなくバグを埋め込みそうな変更ですね… 😩
return "Fizz";
}elseif ($val % 5 === 0){
return "Buzz";
+ }elseif($val % 7 === 0){
+ return "Hoge!";
}else{
return "$val";
}
発展型
ここでは省略しますが、さらにオブジェクト指向的な考えとして、条件をオブジェクト化する
というのもあります。
各Speakerクラスのsupportメソッドの内容をクラスの外から渡せるようにする、というイメージです。
ラムダや関数オブジェクトを渡すというのもありですね。
外から条件を渡せるようになると、条件の組み換えをクラスの外からできるようになるので、かなり汎用性を持つことになります。
まとめ
カルテット流FizzBuzzの回答例はどうでしたか?
FizzBuzzだとあまり効果が分かりづらいかと思いますが、DIやテストコードと組み合わせると効果がわかるようになるかと思います。
ココだけの話、バックエンドエンジニアの実技試験は、「オブジェクト指向でどれだけ書けるか」というのも見るのですが、
今回提示したぐらい書けると良い印象になるかと思います(保証はできませんが… 😔)
もちろん、FizzBuzz問題ではありませんのであしからず。