avatarEmanuel Trandafir

Summary

The context explains the implementation of the FizzBuzz problem using the Open-Closed Principle, immutability, encapsulation, and unit tests, focusing on good coding practices and design principles.

Abstract

The FizzBuzz problem is a common interview question for junior developers, where the task is to iterate through a list of numbers from 1 to 100 and print specific outputs based on divisibility by 3, 5, or both. The context describes an approach to solving this problem by separating the method that converts a number to the desired string from the one that writes the result into the console, making the code testable and adhering to the Open/Closed principle. The implementation involves creating a Divider class with private and final fields for output and divisor, ensuring immutability. This allows for the creation of a list of Divider objects that can be used in the FizzBuzz class to concatenate the output of its dividers or return the number itself if none were found. The context also covers unit testing and the flexibility of this approach, which allows for adding, removing, or changing the order of dividers with minimal changes to the code.

Bullet points

  • The FizzBuzz problem is a common interview question for junior developers.
  • The initial requirement is to iterate through a list of numbers from 1 to 100 and print specific outputs based on divisibility by 3, 5, or both.
  • The context describes an approach to solving this problem using the Open-Closed Principle, immutability, encapsulation, and unit tests.
  • The implementation involves creating a Divider class with private and final fields for output and divisor, ensuring immutability.
  • The FizzBuzz class uses the list of Divider objects to concatenate the output of its dividers or return the number itself if none were found.
  • The context covers unit testing and the flexibility of this approach, which allows for adding, removing, or changing the order of dividers with minimal changes to the code.
  • The solution adheres to good coding practices and design principles, such as the Single Responsibility Principle and the Open/Closed Principle.

Explaining the Open-Closed Principle to the Rubber Duck With a Hands-On Exercise

Understanding how the Opened-Closed principle, immutability, encapsulation, and unit tests are working with a simple, hands-on, interview problem.

Photo by Brett Jordan on Unsplash

1. Overview

FizzBuzz is a common interview question for junior developers. The initial requirement is to iterate through a list of numbers from 1 to 100 and print in the console:

  • Fizz — if the number is divisible by 3
  • Buzz — if the number is divisible by 5
  • FizzBuzz — if the number is divisible by both 3 and 5
  • the number itself if no criteria were met

After that, the interviewer often adds extra requirements and use-cases, to see how easy it is for you to adapt and implement the new features.

2. The Design

Even though it can be tempting to start coding using a bunch of if statements and solve the problem quickly, let’s try to take a step back and think about it first.

Photo by Sven Mieke on Unsplash

Firstly, we want to show that our code is testable. Asserting the console output can be hard and it will clutter the tests. Therefore, we’ll try to separate the method which converts a number to the desired string from the one that writes the result into the console.

Secondly, the dividers are a potential point where the requirements might change. Consequently, we’ll try to allow the creation of new dividers without affecting the rest of the code — conforming to the Open/Closed principle.

3. The Implementation

Let’s start with the Divider class. It has a String “output” field and an integer “divisor”.

They are both private and final, immutable. This means that, once a Divider is created, we can no longer change its output or the way is checking if the output needs to be printed.

As a result, we will be able to create a list of Divider objects like this:

List<Divider> dividers = List.of(
    new Divider("Fizz", 3),
    new Divider("Buzz", 5)
);

Now, let’s take a look at the FizzBuzz class that s using these dividers:

For a given number, we’ll concatenate the output of its dividers or return the number itself if none were found.

4. Unit Testing

It is now very simple to test the getOutput() method. In my case, I have used Junit5’s parameterized test that allows me to specify pairs of input and expected output — but can easily be done manually in any testing framework:

5. The Flexibility

Even if it doesn’t look like much, this structure offers us a lot of flexibility.

We can add new Dividers just by creating new instances of the Divider object. For instance, we can print “Foo” for multiples of 7 with the code below:

List<Divider> dividers = List.of(
    new Divider("Fizz", 3),
    new Divider("Buzz", 5),
    new Divider("Foo", 7)
);
FizzBuzz fizzBuzz = new FizzBuzz(dividers);

We can also remove dividers without touching the “production” code: we just need to remove them from the list when instantiating the FizzBuzz object. The various dividers are now simple plugins for our application.

Furthermore, we can change the order of outputs of the resulting string just by changing the order of the dividers from the list.

All this flexibility of changing the behavior of the application with minimal changes is a result of conforming to the Open/Closed and Single Responsibility principles.

6. Conclusion

In this article, we implemented a simple FizzBuzz while focusing on good coding practices, design, and principles.

Following the SOLID principles, especially the first two, and keeping an eye on testability lead to a decoupled and flexible design where we can easily adapt to new requirements with minimal risk.

In the next article, we’ll see how to handle unexpected changes in requirements and how to use dependency inversion and polymorphism to solve the implement them.

Java
Python
JavaScript
Programming
Software Development
Recommended from ReadMedium