Stefano Tommesani

  • Increase font size
  • Default font size
  • Decrease font size
Home OOD / OOP An object-oriented approach to FizzBuzz

An object-oriented approach to FizzBuzz

Hits

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  

Latest Articles

A software to stand out 27 January 2018, 14.35 Web
A software to stand out
Standing out of the pack starts by being visible, and being noticed by the right group of professionals. No matter how good your profile is, it is lost in a sea of similar profiles, so you need to show up and start attracting
Web page scraping, the easy way 07 January 2018, 00.46 Web
Web page scraping, the easy way
There are many ways to extract data elements from web pages, almost all of them prettier and cooler than the method proposed here, but as we are in an hurry, let's get that data quickly, ok? Suppose we have to extract the
Scraping dynamic page content 06 January 2018, 23.57 Web
Scraping dynamic page content
One of the most common roadblocks when scraping the content of web sites is getting the full contents of the page, including JS-generated data elements (probably, the ones you are looking for). So, when using CEFSharp to scrape
Unit-testing file I/O 26 November 2017, 12.09 Testing
Unit-testing file I/O
Two good news: file I/O is unit-testable, and it is surprisingly easy to do. Let's see how it works! A software no-one asked for First, we need a piece of software that deals with files and that has to be unit-tested. The
Fixing Git pull errors in SourceTree 10 April 2017, 01.44 Software
Fixing Git pull errors in SourceTree
If you encounter the following error when pulling a repository in SourceTree: VirtualAlloc pointer is null, Win32 error 487 it is due to to the Cygwin system failing to allocate a 5 MB large chunk of memory for its heap at
View Stefano Tommesani's profile on LinkedIn