Pi Calculator Kata (OOP)
I was cruising code review, looking for an interesting problems and the heavens certainly delivered:
Greg Beech says here that he asks C# candidates to produce a formula that calculates PI Given that Pi can be estimated using the function 4∗(1−1/3+1/5−1/7+…) You can see the answers here:
I went for an OOP approach to solve the problem.
But before I did that, we must answer the question: what should the code look like?
What should good code look like?
Here’s what I think all good code should do:
- first and foremost it must be understandable. Most of the code you write will be read many more times than edited. It’s gotta be understandable without having to grok anything.
- It must be easy to maintain
- and if required must be easy to make changes
- Must be testable in some way.
- You want to be able to write the thing and get out as soon as you can, while allowing it to be maintainable in the future.
Points about the code:
• API is simpler to read and understand. The main logic is hidden away.
• Easily amenable to change. What if the boss walks in and says he/she wants every 4th fraction to be: 1/e - will your code easily be able to handle that? I’d simply add a new ElementN sub-class called ElementE and add a conditional to my factory method and the changes would be made instantly.
• Employs a polymorphic approach (read: pure OOP) way to handle the problem.
Here is the code
namespace BKTest
{
public class Pie
{
/// <summary>
/// Estimates pi based on the number of fractions we desire it to estimate by.
/// The way I view it: you basically have 4 * (element0 + element1 + element2 etc.)
/// where element0, element1 etc are instances of the Element class.
/// I use a factory method to instantiate the element types and use polymorphism to differentiate
/// between the two different types of elements that are currently out there: Element0 and the others: Element1, Element2 etc .
/// Element zero is different because you can't divide by zero! (This probably won't make any sense)
/// Till you attempt the problem yourself.
///
/// </summary>
/// <param name="elementCount"></param>
/// <returns></returns>
public double Estimate(int elementCount)
{
ElementCollection ec = new ElementCollection(elementCount);
return 4 * ec.AddAllElements();
}
}
public class ElementCollection
{
private int elementCount;
public ElementCollection(int elementCount)
{
this.elementCount = elementCount;
}
public double AddAllElements()
{
double result = 0.0;
for (int i = 0; i < elementCount + 1; i++)
{
ElementN element = ElementFactory(i);
result += element.Value();
}
return result;
}
public ElementN ElementFactory(int i)
{
if (i == 0)
{
return new Element0(i);
}
else
{
bool even = (i % 2 == 0);
if (even)
{
return new ElementEven(i);
}
else
{
return new ElementOdd(i);
}
}
}
public class Element0 : ElementN
{
public Element0(int elementCount)
: base(elementCount)
{
}
public override int Sign()
{
return 1;
}
public override double PosivitveValue()
{
return 1.0;
}
}
public class ElementEven : ElementN
{
public ElementEven(int elementCount)
: base(elementCount)
{
}
public override int Sign()
{
return 1;
}
}
public class ElementOdd : ElementN
{
public ElementOdd(int elementCount)
: base(elementCount)
{
}
public override int Sign()
{
return -1;
}
}
public abstract class ElementN
{
private int elementCount;
public ElementN(int elementCount)
{
this.elementCount = elementCount;
}
virtual public double Value()
{
return Sign() * PosivitveValue();
}
virtual public double PosivitveValue()
{
return ((1.0) / (2.0 * elementCount + 1));
}
/// <summary>
/// Either the sign is positive or negative
/// We could probably put this into its own class
/// and have a factory method but we'll keep it like this for the moment
/// till one day change requires us to change it. After all, a sign can only
/// either be positive or negative. (apparently you can also multiple by the
/// square root of (-1) but that's another matter.
/// </summary>
/// <returns></returns>
abstract public int Sign();
}
}
}
Here are the tests:
using NUnit.Framework;
namespace BKTest
{
[TestFixture]
internal class PieTest
{
[Test]
[TestCase(0, 4)]
[TestCase(1, 4 * (1 - 1.00 / 3.0))]
[TestCase(2, 4 * (1 - 1.00 / 3.0 + 1 / 5.0))]
[TestCase(3, 4 * (1 - 1.00 / 3.0 + 1 / 5.0 - 1 / 7.0))]
[TestCase(4, 4 * (1 - 1.00 / 3.0 + 1 / 5.0 - 1 / 7.0 + 1 / 9.0))]
public void Estimate_1Parameter_expect_fourMinusOneThird(int input, double output)
{
// set up
double result = new Pie().Estimate(input);
Assert.AreEqual(output, result);
}
}
}