Stefano Tommesani

  • Increase font size
  • Default font size
  • Decrease font size
Home Programming An object-oriented approach to FizzBuzz

An object-oriented approach to FizzBuzz

FizzBuzz is described as:

Write a program that prints the integers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz"

It takes a couple of minutes to write a few lines of C++ code that satisfy the requirements:

#include <iostream>
 
using namespace std;
int main () {
 
 int i;
 for (i = 0; i <= 100; i++) {
 if ((i % 15) == 0)
 cout << "FizzBuzz" << endl;
 else if ((i % 3) == 0)
 cout << "Fizz" << endl;
 else if ((i % 5) == 0)
 cout << "Buzz" << endl;
 else
 cout << i << endl;
 }
 return 0;
} 

But then the examiner may ask you to add some more checks, such as printing a different string when the number is a multiple of 11 or any other number, and the code quickly becomes ugly. In fact, the major mistake that you can make with FizzBuzz is using the wrong sequence of checks, for example checking for modulus of 3 before that of 15, so that FizzBuzz is never printed; this is obvious with only 3 simple checks, but gets more complicated with a longer list of more complicated checks.

So why not spend a few minutes more than those required to write a trivial solution, and propose a fully object-oriented solution that is designed for extensibility?

FizzBuzzClass1

The proposed solution packs each check in a class that is derived by an abstract base class (BaseCheck), and the checking is encapsulated in the RunCheck method. Finally, another class named NumCheckCollection maintains a list of checks and runs through them for each number in the given list.

#include <list>
#include <iostream>

using namespace std;

class BaseCheck {
public:
virtual bool RunCheck(const int CheckValue) = 0;
};

class Num3Check : public BaseCheck {
bool RunCheck(const int CheckValue);
};

class Num5Check : public BaseCheck {
bool RunCheck(const int CheckValue);
};

class Num15Check : public BaseCheck {
bool RunCheck(const int CheckValue);
};

class NumCheckCollection {
public:
NumCheckCollection();
~NumCheckCollection();
void AddNumCheck(BaseCheck *NewCheck);
bool RunChecks(const int CheckValue);
private:
list<BaseCheck *> ChecksList;
};

bool Num3Check::RunCheck(const int CheckValue)
{
if ((CheckValue % 3) == 0)
{
cout << "Fizz" << endl;
return true;
} else
return false;
}

bool Num5Check::RunCheck(const int CheckValue)
{
if ((CheckValue % 5) == 0)
{
cout << "Buzz" << endl;
return true;
} else
return false;
}

bool Num15Check::RunCheck(const int CheckValue)
{
if ((CheckValue % 15) == 0)
{
cout << "FizzBuzz" << endl;
return true;
} else
return false;
}

void NumCheckCollection::AddNumCheck(BaseCheck *NewCheck)
{
ChecksList.push_back(NewCheck);
}

bool NumCheckCollection::RunChecks(const int CheckValue)
{
bool SuccessfulCheck = false;
for (auto ChecksListIterator = ChecksList.begin(); ChecksListIterator != ChecksList.end(); ++ChecksListIterator)
{
if ((*ChecksListIterator)->RunCheck(CheckValue))
{
SuccessfulCheck = true;
break;
}
}
return SuccessfulCheck;
}

NumCheckCollection::NumCheckCollection()
{

}

NumCheckCollection::~NumCheckCollection()
{
for (auto ChecksListIterator = ChecksList.begin(); ChecksListIterator != ChecksList.end(); ++ChecksListIterator)
delete *ChecksListIterator;
ChecksList.clear();
}

int _tmain(int argc, _TCHAR* argv[])
{
NumCheckCollection CheckCollection;
CheckCollection.AddNumCheck(new Num15Check());
CheckCollection.AddNumCheck(new Num5Check());
CheckCollection.AddNumCheck(new Num3Check());

for (int Counter = 1; Counter <= 100; Counter++)
{
if (!CheckCollection.RunChecks(Counter))
{
cout << Counter << endl;
}
}

getchar();
return 0;
}

This must be the longest FizzBuzz implementation ever... But let's have a look at the pros:

  • each check is encapsulated in a clearly-named class, so the declaration of the sequence of checks becomes much clearer, as the calls to the AddNumCheck method show the exact sequence of checks performed
  • this class design can support any other check, just inherit another class from BaseCheck and define the RunCheck method, without messing with the existing code
  • it gives you an opportunity to discuss several OO design choices,  such as abstract base classes

FizzBuzzClass2

Still, there is a lot of similar code in the 3 concrete classes, so we can refactor the code to move the bulk of the RunCheck method in a base class, and greatly simplify the implementation of the derived classes:

#include <list>
#include <iostream>
#include <string>

using namespace std;

class BaseCheck {
public:
virtual bool RunCheck(const int CheckValue) = 0;
virtual ~BaseCheck() {};
};

class ModulusCheck : public BaseCheck {
public:
ModulusCheck(const int ModulusValue, const string MessageValue);
bool RunCheck(const int CheckValue);
~ModulusCheck() {};
private:
int Modulus;
string Message;
};

class Modulus3Check : public ModulusCheck {
public:
Modulus3Check() : ModulusCheck(3, "Fizz") {};
~Modulus3Check() {};
};

class Modulus5Check : public ModulusCheck {
public:
Modulus5Check() : ModulusCheck(5, "Buzz") {};
~Modulus5Check() {};
};

class Modulus15Check : public ModulusCheck {
public:
Modulus15Check() : ModulusCheck(15, "FizzBuzz") {};
~Modulus15Check() {};
};

class NumCheckCollection {
public:
NumCheckCollection();
~NumCheckCollection();
void AddNumCheck(BaseCheck *NewCheck);
bool RunChecks(const int CheckValue);
private:
list<BaseCheck *> ChecksList;
};

ModulusCheck::ModulusCheck(const int ModulusValue, const string MessageValue)
{
Modulus = ModulusValue;
Message = MessageValue;
}

bool ModulusCheck::RunCheck(const int CheckValue)
{
if ((CheckValue % Modulus) == 0)
{
cout << Message << endl;
return true;
} else
return false;
}

void NumCheckCollection::AddNumCheck(BaseCheck *NewCheck)
{
ChecksList.push_back(NewCheck);
}

bool NumCheckCollection::RunChecks(const int CheckValue)
{
bool SuccessfulCheck = false;
for (auto ChecksListIterator = ChecksList.begin(); ChecksListIterator != ChecksList.end(); ++ChecksListIterator)
{
if ((*ChecksListIterator)->RunCheck(CheckValue))
{
SuccessfulCheck = true;
break;
}
}
return SuccessfulCheck;
}

NumCheckCollection::NumCheckCollection()
{
}

NumCheckCollection::~NumCheckCollection()
{
for (auto ChecksListIterator = ChecksList.begin(); ChecksListIterator != ChecksList.end(); ++ChecksListIterator)
delete *ChecksListIterator;
ChecksList.clear();
}

int _tmain(int argc, _TCHAR* argv[])
{
NumCheckCollection CheckCollection;
CheckCollection.AddNumCheck(new Modulus15Check());
CheckCollection.AddNumCheck(new Modulus5Check());
CheckCollection.AddNumCheck(new Modulus3Check());

for (int Counter = 1; Counter <= 100; Counter++)
{
if (!CheckCollection.RunChecks(Counter))
{
cout << Counter << endl;
}
}

getchar();
return 0;
}

The NumCheckCollection directly deletes the instances of ModulusXCheck in its destructor calling a pointer to the base class, so it is necessary to create a virtual destructor to ensure that proper clean-up is performed.

But are derived classes (Modulus15Check, Modulus5Check and Modulus3Check) really necessary? Actually, they are not, as you can replace

 CheckCollection.AddNumCheck(new Modulus15Check()); CheckCollection.AddNumCheck(new Modulus5Check()); CheckCollection.AddNumCheck(new Modulus3Check()); 

with

 CheckCollection.AddNumCheck(new ModulusCheck(15, "FizzBuzz"));
CheckCollection.AddNumCheck(new ModulusCheck(5, "Buzz"));
CheckCollection.AddNumCheck(new ModulusCheck(3, "Fizz"));

so that the declaration of concrete classes is not needed anymore. But I think this is a mistake, as it mixes the declaration of the behaviour of each class with the creation of class instances, requiring the user to know how each is implemented instead of just using them. Please note that these examples are trivial, as the checks in FizzBuzz are extremely simple, but in a real-world solution these checks may be a lot more complex, using far more parameters, so the importance of adding a layer of abstraction becomes clearer.

Still, are these classes really necessary to model such a simple behaviour? In fact, we can get rid of them and just use function pointers. The following solution is still scalable and can be easily extended, as additional behaviours can be modeled with more functions sharing the calling parameters (as detailed in the CheckFunction typedef), but does not use any OOD:

#include <list>
#include <iostream>
#include <string>

using namespace std;

bool GenericModulusCheck(const int CheckValue, const int RefValue, const string MessageValue)
{
if ((CheckValue % RefValue) == 0)
{
cout << MessageValue << endl;
return true;
} else
return false;
}

bool Modulus3Check(const int CheckValue)
{
return GenericModulusCheck(CheckValue, 3, "Fizz");
}

bool Modulus5Check(const int CheckValue)
{
return GenericModulusCheck(CheckValue, 5, "Buzz");
}

bool Modulus15Check(const int CheckValue)
{
return GenericModulusCheck(CheckValue, 15, "FizzBuzz");
}

typedef bool (*CheckFunction)(const int CheckValue);

class NumCheckCollection {
public:
NumCheckCollection() {};
~NumCheckCollection() {};
void AddNumCheck(CheckFunction NewCheck);
bool RunChecks(const int CheckValue);
private:
list<CheckFunction> ChecksList;
};

void NumCheckCollection::AddNumCheck(CheckFunction NewCheck)
{
ChecksList.push_back(NewCheck);
}

bool NumCheckCollection::RunChecks(const int CheckValue)
{
bool SuccessfulCheck = false;
for (auto ChecksListIterator = ChecksList.begin(); ChecksListIterator != ChecksList.end(); ++ChecksListIterator)
{
if ((*ChecksListIterator)(CheckValue))
{
SuccessfulCheck = true;
break;
}
}
return SuccessfulCheck;
}

int _tmain(int argc, _TCHAR* argv[])
{
NumCheckCollection CheckCollection;
CheckCollection.AddNumCheck(&Modulus15Check);
CheckCollection.AddNumCheck(&Modulus5Check);
CheckCollection.AddNumCheck(&Modulus3Check);

for (int Counter = 1; Counter <= 100; Counter++)
{
if (!CheckCollection.RunChecks(Counter))
{
cout << Counter << endl;
}
}

getchar();
return 0;
}

Quote this article on your site

To create link towards this article on your website,
copy and paste the text below in your page.




Preview :


Powered by QuoteThis © 2008
Last Updated on Monday, 27 May 2013 18:51  
View Stefano Tommesani's profile on LinkedIn

Latest Articles

Castle on the hill of crappy audio quality 19 March 2017, 01.53 Audio
Castle on the hill of crappy audio quality
As the yearly dynamic range day is close (March 31st), let's have a look at one of the biggest audio massacres of the year, Ed Sheeran's "Castle on the hill". First time I heard the song, I thought my headphones just got
Necessary evil: testing private methods 29 January 2017, 21.41 Testing
Necessary evil: testing private methods
Some might say that testing private methods should be avoided because it means not testing the contract, that is the interface implemented by the class, but the internal implementation of the class itself. Still, not all
I am right and you are wrong 28 December 2016, 14.23 Web
I am right and you are wrong
Have you ever convinced anyone that disagreed with you about a deeply held belief? Better yet, have you changed your mind lately on an important topic after discussing with someone else that did not share your point of
How Commercial Insight changes R&D 06 November 2016, 01.21 Web
How Commercial Insight changes R&D
The CEB's Commercial Insight is based on three pillars: Be credible/relevant – Demonstrate an understanding of the customer’s world, substantiating claims with real-world evidence. Be frame-breaking – Disrupt the
Windows Forms smells funny, but... 07 April 2016, 15.38 Software
Windows Forms smells funny, but...
In the "2016 .NET Community Report" just released by Telerik, the answers to the question "What technology would you choose if building for Windows Desktop?" were as follows: So roughly half of new desktop developments would

Translate