DEV Community

Cover image for FizzBuzz Refactoring Challenge with Open/Closed Principle
Oliver Mensah
Oliver Mensah

Posted on

FizzBuzz Refactoring Challenge with Open/Closed Principle

Introduction

Have you ever heard of the FizzBuzz refactoring challenge? I encountered it through an article on Medium. The challenge is basically about writing code that allows new requirement to be added to the FizzBuzz problem. And the author did a great job with the strategies he adopted in solving the challenge. In this article, I will share about how the Open/Closed principle(OCP), the 'O' in the SOLID principle acronym, will be very useful in solving this challege.

FizzBuzz Challenge

Mostly, coding up the FizzBuzz solution is easier by just replacing a number that meets a certain condition with a new value. And it usually goes like this; multiple of 3 should be replaced with Fizz, that of 5 with Buzz, and that of both 3 and 5 with FizzBuzz.
Below is code sample for this challenge.

<?php
class FizzBuzz
{
  public function generate(int $limit): void
  {
    for ($number = 1; $number <= $limit; $number++) {
      if ($number % 3 === 0 && $number % 5 === 0) {
        echo "FizzBuzz\n";
      } else if ($number % 3 === 0) {
        echo "Fizz \n";
      } else if ($number % 5 === 0) {
        echo "Buzz\n";
      } else {
        echo "$number \n";
      }
    }
  }
}
$fizzBuzz = new FizzBuzz();
$fizzBuzz->generate(100);
Enter fullscreen mode Exit fullscreen mode

Meeting new Requirements

Then here comes a new requirement to handle, such that if a number is a multiple of 7, output Bazz. For a multiple of 7 and 3, output FizzBazz. And for a multiple of 7 and 5, output BuzzBazz.
All we need to do is just adding more checks then return what is needed to. Below is the code sample we could write for this.

<?php
class FizzBuzz
{
  public function generate(int $limit): void
  {
    for ($number = 1; $number <= $limit; $number++) {
      if ($number % 3 === 0 &&  $number % 5 === 0 && $number % 7 === 0) {
        echo "FizzBuzzBazz\n";
      } else if ($number % 3 === 0 && $number % 5 === 0) {
        echo "FizzBuzz\n";
      } else if ($number % 3 === 0 &&  $number % 7 === 0) {
        echo "FizzBazz\n";
      } else if ($number % 5 === 0 && $number % 7 === 0) {
        echo  "BuzzBazz\n";
      } else if ($number % 3 === 0) {
        echo "Fizz\n";
      } else if ($number % 5 === 0) {
        echo "Buzz\n";
      } else if ($number % 7 === 0) {
        echo "Bazz\n";
      } else {
        echo "$number\n";
      }
    }
  }
}

$fizzBuzz = new FizzBuzz();
$fizzBuzz->generate(100);
Enter fullscreen mode Exit fullscreen mode

It is simple to implement it this way. But when you consider some principles and patterns, this solution is not really scalable. Adhering to code design principes will improve how the code is designed. And that's where the Open/Closed Principle(OCP) of the SOLID principle comes to a rescue.

Adopting Open/Closed Principle to make FizzBuzz extensible

I recently shared on Twitter about exploring deeper into software design and architecture concepts. Through that, I learned a lot about different patterns and principles. One of which is the SOLID principle and within this, we have OCP. The OCP states that you should be able to extend a class' behavior without modifying it. Meaning, a unit of code can be considered open for extension when its behavior can be easily changed without modifying it. The fact that no actual modification is needed to change the behavior of a unit of code makes it “closed” for modification.

In our case, the FizzBuzz class should be able to add more requirement with minimal or no changes to the codebase. To do this, we need to be able to identify what is happening in each condition that determines a strategy and remove what's common into a specific abstraction such as interface, abstract class , and class.

If we use interfaces well, then we can easily drop in new implementations without needing to modify our existing calling code, hence lets wrap the commonalities into an interface. And the commonalities the FizzBuzz class are matching condition and value replacement.

<?php
interface RuleInterface
{
  public function matches(int $number): bool;
  public function getReplacement(): string;
}
Enter fullscreen mode Exit fullscreen mode

After creating the interface, we then allow each specific strategy to implement the interface.

<?php

class FizzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 3 === 0;
  }

  public function getReplacement(): string
  {
    return 'Fizz';
  }
}

class BuzzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 5 === 0;
  }

  public function getReplacement(): string
  {
    return 'Buzz';
  }
}

class BazzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 7 === 0;
  }

  public function getReplacement(): string
  {
    return 'Bazz';
  }
}

class BuzzBazzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 5 === 0 && $number % 7 === 0;
  }

  public function getReplacement(): string
  {
    return 'BuzzBazz';
  }
}

class FizzBazzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 3 === 0 &&  $number % 7 === 0;
  }

  public function getReplacement(): string
  {
    return 'FizzBazz';
  }
}
class FizzBuzzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 3 === 0 && $number % 5 === 0;
  }

  public function getReplacement(): string
  {
    return 'FizzBuzz';
  }
}

class FizzBuzzBazzRule  implements RuleInterface
{
  public function matches(int $number): bool
  {
    return $number % 3 === 0 &&  $number % 5 === 0 && $number % 7 === 0;
  }

  public function getReplacement(): string
  {
    return 'FizzBuzzBazz';
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally we could allow our class to make use of these specifics through dependency injection.

<?php 
class FizzBuzz
{
  private $rules;

  public function __construct(array $rules)
  {
    $this->rules = $rules;
  }
  public function generateList(int $limit): array
  {
    $list = [];
    for ($number = 1; $number <= $limit; $number++) {
      $list[] = $this->generateElement($number);
    }
    return $list;
  }

  private function generateElement(int $number): string
  {
    foreach ($this->rules as $rule) {
      if ($rule->matches($number)) return $rule->getReplacement();
    }
    return (string) $number;
  }
}

$rules = [
  new FizzBuzzBazzRule,
  new FizzBuzzRule(),
  new BuzzBazzRule,
  new FizzBazzRule,
  new FizzRule(),
  new BuzzRule(),
  new BazzRule
];
$fizzBuzz = new FizzBuzz($rules);
$list = $fizzBuzz->generateList(100);
var_dump($list);

Enter fullscreen mode Exit fullscreen mode

At the end, we can introduce different specifics that we want by just implementing the interface and passing into the the main class(FizzBuzz). With this approach, the FizzBuzz class is not likely to be modified to meet the new requirements and that's what Open/Closed principle advocates for.

Conclusion

We should always try as much as possible to identify some patterns in our code when solving new requirements and find out what principles could be useful in improving the codebase.

I enjoyed putting this together and I hope you find it very useful
The sample codes can be found here.

Let's discuss your opinion about this strategy I used, what could have been done better and what you might have used for this problem?
Together we grow

Further Reading

I learned most patterns/principles of class design from Matthias Noback's book on Principles of Package Design, maybe you can check it up

Keep In Touch

Let's keep in touch as we continue to explore and learn more about software engineering. Don't forget to connect with me on Twitter or LinkedIn

Top comments (23)

Collapse
 
apmccartney profile image
Austin McCartney • Edited

I'm not familiar with PHP, but this seems less than ideal. It broke my heart to see you manually combinate the predicates in FizzBuzzRule, FizzBazzRule, BuzzBazzRule, and FizzBuzzBazzRule.

Are you familiar with decorator pattern? I think it expresses this sort of use case pretty elegantly. I took a few minutes to jot down an alternative implementation to demonstrate.

abstract class Rule
{
    abstract protected function apply(int $number, bool $match);

    public function eval(int $number)
    {
        $this->apply($number, false);
    }
}

class Bottom extends Rule
{
    public function apply(int $number, bool $matched)
    {
        if(! $matched) { 
            printf('%d', $number);
        }
        echo PHP_EOL;
    }
}

class Decorator extends Rule
{
    private $name;
    private $number;
    private $nested_rule;

    public function __construct(int $number, string $name, Rule $rule)
    {
        $this->number = $number;
        $this->name = $name;
        $this->nested_rule = $rule;
    }

    public function apply(int $number, bool $matched)
    {
        if ($number % $this->number == 0) {
            echo $this->name;
            $this->nested_rule->apply($number, True);
        } else {
            $this->nested_rule->apply($number, $matched);
        }
    }
}

$FizzBuzzBazzRule = 
   new Decorator(3, 'Fizz', 
     new Decorator(5, 'Buzz', 
       new Decorator(7, 'Bazz', 
         new Bottom())));

$FizzBuzzBazzRule->eval(3);
$FizzBuzzBazzRule->eval(5);
$FizzBuzzBazzRule->eval(7);
$FizzBuzzBazzRule->eval(15);
$FizzBuzzBazzRule->eval(21);
$FizzBuzzBazzRule->eval(35);
$FizzBuzzBazzRule->eval(105);
$FizzBuzzBazzRule->eval(19);

This snippet has the expected output of

Fizz
Buzz
Bazz
FizzBuzz
FizzBazz
BuzzBazz
FizzBuzzBazz
19

Using a decorator this way has a few advantages:

  • there are fewer class definitions (3 vs 9) and less code overall (59 vs 134) to maintain and test.
  • adding a new check (e.g. values divisible by 11 print bar) does not require defining a new class. The implementation you suggest seems to require defining a combinatorially increasing number classes to accommodate a new check.
  • reordering the checks does require defining a new class, just a new instance, e.g.
$BuzzFizzBazzRule =  
   new Decorator(5, 'Buzz', 
     new Decorator(3, 'Fizz',
       new Decorator(7, 'Bazz', 
         new Bottom())));
Collapse
 
olivermensahdev profile image
Oliver Mensah

That's awesome. I love this approach too. You can literally add a new requirement without writing a new class. Very elegant code but seems confused at first glance with the boolean flags been added. Maybe you can throw more light on the flag, why they exist?

Collapse
 
apmccartney profile image
Austin McCartney • Edited

Sure Thing!

Let's consider a call to FizzBuzzBazzRule to see what that boolean flag is doing.

$FizzBuzzBazzRule->eval(5)

We begin by calling to the public facing eval method defined in the Rule abstract class.

public function eval(int $number)
{
    $this->apply($number, false);
}

This method forwards the argument to the protected apply method, but also passes a second boolean parameter False.

This boolean parameter is used to indicate whether any previous rules have successfully matched against the $number argument.

Because eval is called by the client, no previous rules have run and consequently, the $number has not yet matched against any previous rule.

abstract protected function apply(int $number, bool $match);

Note that the apply method is marked as abstract. That means that we'll need to determine what method to call by considering the child class of which the object is an instance. In this case, the child class is a Decorator with $name == 'Fizz' and $number == 3. So let's consider the apply method of the Decorator.

public function apply(int $number, bool $matched)
{
    if ($number % $this->number == 0) {
        echo $this->name;
        $this->nested_rule->apply($number, True);
    } else {
        $this->nested_rule->apply($number, $matched);
    }
}

We check whether the $number argument is divisible by the value of the $this->number member variable. 5 is not divisible by 3 so we take the else branch of the conditional. We pass forward the $number argument to the next rule along with the $matched boolean indicating whether any previous rule (which is still False).

The next rule is a Decorator with $name == 'Buzz' and $number == 5. 5 is divisible by 5 so we take the first branch of the conditional. Again we pass forward the $number argument to the next rule, but this time, because we've found a match, we pass True as the boolean argument.

The next rule is a Decorator with $name == 'Bazz' and $number == 7. 5 is not divisible by 7 so again we take the else branch of the conditional. We pass forward the $number argument to the next rule along with the $matched boolean indicating whether any previous rule (which is now True).

The last rule is a Bottom. This class serves as a base case for this sort of object-oriented recursion. Let's consider it's apply method.

public function apply(int $number, bool $matched)
{
    if(! $matched) { 
        printf('%d', $number);
    }
    echo PHP_EOL;
}

If no previous rule has matched the argument $number (as indicated by the $matched argument), we need to print the value of $number to the console. In either case, we print an end of line character to console to complete an iteration of the fizzbuzz loop.

Thread Thread
 
olivermensahdev profile image
Oliver Mensah

Thanks for the explanation. Very impressive. I checked your profile to get your Twitter account but it seems you are not on Twitter yet. I got to follow you so we can engage in discussions in the future.

Thread Thread
 
apmccartney profile image
Austin McCartney

twitter.com/RakishRhenoplos

Happy to chat anytime. =)

Collapse
 
mrspartak profile image
Spartak

Oop also leads to a big files/instances spread. So if yiu return to your code in a year, I guess the first "ugly" one file code will take a couple minutes to get you an idea what's going on. But classes will take much more time to track all dependencies across files. So not every solution needs oop in it of course

Collapse
 
srleyva profile image
Stephen Leyva (He/Him)

This is actually something we’ve discussed heavily at places where I’ve worked. Usually implementation like this makes it harder to read (which is interesting considering the arguments for SOLID are maintainability). I’ve seen these patterns beneficial in libraries to be consumed and projects that are a massive scale where design principles help with on-boarding. For smaller things, seems like overkill.

Collapse
 
tomcools profile image
Tom Cools

The easier solution is in the article he mentioned: itnext.io/fizzbuzzbazz-how-to-answ... (String Concatenation).

Thread Thread
 
srleyva profile image
Stephen Leyva (He/Him)

Oh yah this isn’t a dig at the authors post at all. Much can be gleaned from applying design principles to a simple problem for education sake. I was commenting on design patterns in general. They are another tool in a developers toolbox, nothing more. The article did a great job of highlighting one of the SOLID principles.

Collapse
 
olivermensahdev profile image
Oliver Mensah

Thanks for the feedback. But I don't think I will write the code this way in production. Instead of putting this in one file. Each class will have its own file.
Though these classes don't have dependencies even if they have, tracking them is easier in modern editors/IDEs.
Based on the feedback I think you might go with a functional approach which is also a good tradeoff.

Collapse
 
mrspartak profile image
Spartak

In our production, we do not choose only one approach. As I mentioned, every method has its pros and cons. And OOP approach for fast/draft release is a tortoise. For this purpose, we, of course, use functions.
Programming is about learning and using instruments for their purpose not to choose only one tool to rule them all :)
And of course, refactoring is a big drawback lately, but it is an excellent teacher. You will never write perfectly from the first time, but you should learn how to write the first time to get your app working and don't spend on it a year

Thread Thread
 
olivermensahdev profile image
Oliver Mensah

At my end, it is mostly OOP. I started with procedural then later joined the OOP approach. I would love to see how different approaches are combined in one project. In your free time, you should enlighten me about that. Let's talk.

Thread Thread
 
mrspartak profile image
Spartak

I quit PHP dev three years ago, last stuff we made was a super simple MVC framework to build a simple protected stacks [auth/reg/acl/db/pages].
So OOP is used for:

  • base Db instance (so a developer can switch between Mongo/Postgres/Mysql). And yes, of course, many ORM libs solve this problem, but we had a team, that must start developing "yesterday" and they well experienced in raw SQL, Mongo data queries and didn't have time to learn new instruments.
  • Acl. But it is super simple, not extendable and used just to save its state.
  • Controllers. This is the most advanced part, they are good extendable and give some flexibility in Controller-Action (also Security, Templating, i18l etc.) And this is just a couple Classes/Interfaces
  • Any API, other Models are easy to declare in only one folder and you ready to use it anywhere

And most of the work is done by functions:

  • Init state of the app
  • working with sessions
  • autoloading
  • any repeatable data manipulations
  • etc

So an example of prototyping. This project, of course, is a mess for OOP programmer, everything can be refactored to OOP if needed and gain more complexity.
So one of these panels needed in automatically logging everything, that happens under the hood. So in just 10 minutes, my colleague wrote a solution directly in init function, that grabs data from the controller and write everything in file. He could create a Log interface, think of logging not only in a file, extend it and create FileLogger. Create some hook for middleware in init app, extend some base middleware model and then log there and spent on it from 1 hour to 2 days, because in that way some code could not work correctly and some part of the engine must be rewritten.
Now, when this thing is working for a year, they want to connect all the panels to Grafana, so they refactored code and did this part in OOP and can send data anywhere they want.
OOP is excellent and solves many problems, but when the language allows you to save your time, write faster and more productive, why not using it?

And you know, this OOP-function skeleton of 2MB still serves its purpose and allows to create fast panels.

Collapse
 
tomcools profile image
Tom Cools

I like this article for what you are trying to do.
Those principles are tricky to teach/show/learn because you always run into the problem of "How am i going to show this". What often happens is either:

  • You showcase the principle in a big application, where it could be useful but then your readers need to know a lot about the business logic of that application. This hinders learning
  • You showcase the principle in a small, trivial application (what you did) but then people will complain your solution is "a typical case of over-engineering" and "a clear example of why OOP is ugly".

Oliver mentions that we should "try as much as possible to identify some patterns in our code when solving new requirements and find out what principles could be useful in improving the codebase".
The main principle I follow is KISS => Keep it Stupid Simple. Following that principle, I always go for String Concatenation for the FizzBuzz problem. See: itnext.io/fizzbuzzbazz-how-to-answ...

Collapse
 
galoisgirl profile image
Anna

In real life, you gain some experience and can generally predict in what direction the software might go in the future.

Given FizzBuzz, I'd guess that business asked me to handle divisibility by 3 and divisibility by 5, so it's just a matter of time until they want divisibility by 7. However, it's not very probable they will want to add a case for when a number is a square.

That would lead my to a solution based on the general principle of prime factors, and not the special rules, so basically string concatenation.

If business wants squares, I'll say "well, that's very different from the things we had until now, so this will take some time".

Collapse
 
olivermensahdev profile image
Oliver Mensah

Sure but as the requirement keeps changing, I will start identifying patterns and how best to prepare for future changes without changing the code too much.

Collapse
 
melvin2016 profile image
MELVIN GEORGE

Nice explanation 👌

Collapse
 
molopony profile image
molopony

The rules should be in reverse order, otherwise the combined rules won't be evaluated.

Collapse
 
olivermensahdev profile image
Oliver Mensah

Awesome thanks for the input

Collapse
 
geekpius profile image
Fiifi Pius

Thanks for enlightening us on the idea of using SOLID to solve problems. But for this principles work best for larger projects. But it's great to know how to implement it.

Collapse
 
olivermensahdev profile image
Oliver Mensah

Welcome chief. #WeRockTogether

Some comments may only be visible to logged-in visitors. Sign in to view all comments.