I've tried to write a configurable OOP-style FizzBuzz program in PHP for learning purposes. I hope everything is quite easy to understand.
- What do you think of the overall architecture?
- Are OOP principles kept well?
- What can be improved?
- How could error-handling and input-checking (i.e. currently you can break the program with ill-formed
$multipliers
array) be improved?
<?php
interface FizzBuzzable
{
public function printMe();
}
class NumberFizzBuzzItem implements FizzBuzzable
{
private $number;
public function __construct($number)
{
$this->number = $number;
}
public function printMe()
{
return (string) $this->number;
}
}
class WordFizzBuzzItem implements FizzBuzzable
{
private $word;
public function __construct($word)
{
$this->word = $word;
}
public static function check($number, $multiplier)
{
return !($number % $multiplier);
}
public function printMe()
{
return $this->word;
}
}
class FizzBuzzPrinter
{
private $items = array();
public function add(FizzBuzzable $item)
{
$this->items[] = $item;
}
public function printMe()
{
$output = '';
foreach ($this->items as $item) {
$output .= $item->printMe();
}
return $output;
}
public function __toString()
{
return $this->printMe();
}
public function isEmpty()
{
return empty($this->items);
}
}
class FizzBuzzFactory
{
private static $multipliers = array();
public static function init(array $multipliers)
{
self::$multipliers = $multipliers;
}
public static function create($number)
{
$printer = new FizzBuzzPrinter();
foreach (self::$multipliers as $multiplier => $word) {
if (WordFizzBuzzItem::check($number, $multiplier)) {
$printer->add(new WordFizzBuzzItem($word));
}
}
if ($printer->isEmpty()) {
$printer->add(new NumberFizzBuzzItem($number));
}
return $printer;
}
}
class FizzBuzzIterator implements Iterator
{
private $from;
private $to;
private $number;
public function __construct($from, $to, $multipliers)
{
$this->from = $from;
$this->to = $to;
FizzBuzzFactory::init($multipliers);
}
public function current()
{
return FizzBuzzFactory::create($this->number);
}
public function rewind()
{
$this->number = $this->from;
}
public function key()
{
return null;
}
public function next()
{
++$this->number;
}
public function valid()
{
return $this->number <= $this->to;
}
}
foreach (new FizzBuzzIterator(1, 100, array(3 => 'Fizz', 5 => 'Buzz')) as $fb) {
echo $fb . "\n";
}
Update:
I modified the code according to @ChrisWue suggestions. The FizzBuzzPrinter
class is gone as we don't actually need concatenation by the rules (this was premature optimization), check()
method has been moved to FizzBuzzFactory
which isn't static anymore. FizzBuzzable
interface became FizzBuzzItem
abstract class because a __toString()
method implementation which was removed along with FizzBuzzPrinter
is still needed for all concrete classes. Business logic was also changed a bit: krsort()
is applied to $multipliers
array in a factory constructor to ensure that bigger multipliers are processed first.
<?php
abstract class FizzBuzzItem
{
abstract public function printMe();
public function __toString()
{
return $this->printMe();
}
}
class NumberFizzBuzzItem extends FizzBuzzItem
{
private $number;
public function __construct($number)
{
$this->number = $number;
}
public function printMe()
{
return (string) $this->number;
}
}
class WordFizzBuzzItem extends FizzBuzzItem
{
private $word;
public function __construct($word)
{
$this->word = $word;
}
public function printMe()
{
return $this->word;
}
}
class FizzBuzzFactory
{
private $multipliers = array();
public function __construct(array $multipliers)
{
$this->multipliers = $multipliers;
krsort($this->multipliers);
}
public static function check($number, $multiplier)
{
return !($number % $multiplier);
}
public function create($number)
{
foreach ($this->multipliers as $multiplier => $word) {
if (self::check($number, $multiplier)) {
return new WordFizzBuzzItem($word);
}
}
return new NumberFizzBuzzItem($number);
}
}
class FizzBuzzIterator implements Iterator
{
private $from;
private $to;
private $number;
private $factory;
public function __construct($from, $to, $multipliers)
{
$this->from = $from;
$this->to = $to;
$this->factory = new FizzBuzzFactory($multipliers);
}
public function current()
{
return $this->factory->create($this->number);
}
public function rewind()
{
$this->number = $this->from;
}
public function key()
{
return null;
}
public function next()
{
++$this->number;
}
public function valid()
{
return $this->number <= $this->to;
}
}
foreach (new FizzBuzzIterator(1, 100, array(3 => 'Fizz', 5 => 'Buzz', 15 => 'FizzBuzz')) as $fb) {
echo $fb . "\n";
}