Disclaimer: Diejenigen, die sich gerne unvorbelastet an die FizzBizz Coding Kata heranwagen möchten, sollten hier besser nicht weiterlesen. Der Artikel wird verschiedene Lösung zeigen
Einleitung
Unser firmeninternes Coding Dojo hatte eine relativ einfache Kata: FizzBuzz. Zwar ist die Aufgabe einfach und überschaubar, aber dafür sind die Erweiterungen vielfältig. Bei dieser Kata konnte man das Prinzip von Unit Test und Test Driven Development verdeutlichen. Erweitert man die Implementierung, kann die Implementierung der alten Anforderungen direkt getestet werden. Man merkt also, ob man über die neue Anforderung die alten Anforderungen vergessen hat. Als Organisator habe ich mir in Vorfeld Gedanken über die mögliche Implementierung gemacht, da ich einer der beiden Programmierer war. Bei der Kata musst ich dafür Sorgen, dass die Schnittstellen/Vorgaben so definiert sind dass der Test keine weiteren Vorgaben für die Implementierung macht als die Schnittstelle. Folgendes Klassengerüst war also vorgegeben:
public class FizzBuzzer
{
public IEnumerable<string> FizzBuzz(int maxZahl)
{
throw new NotImplementedException();
}
}
Das Pair-Programming-System ist auch in ein verändertes Ping-Pong geändert worden: Ich habe einen Test geschrieben, mein Co-Programmierer hat den Test implementiert und ich habe den nächsten Test geschrieben.
Dojo Lösung
Die Lösung im Dojo war die einfachste aller Möglichkeiten…eine Implementierung der Methode und das wars. Selbst als die Erweiterungen kamen wurde der FizzBuzzer höchstens um zwei Konstruktor-Parameter erweitert. Und damit die Tests laufen gabe es einen Default-Konstruktor mit dem Standard “3″ und “5″.
public class FizzBuzzer
{
#region Felder
private int _n;
private int _m;
#endregion
#region Konsturktor: (), (n, m)
public FizzBuzzer() : this(3,5)
{
}
public FizzBuzzer(int n, int m)
{
_n = n;
_m = m;
}
#endregion
public IEnumerable<string> FizzBuzz(int maxNumber)
{
//Plausibilitäts prüfung
if (maxNumber < 1)throw new ArgumentException("Nur Zahlen größer 0 sind erlaubt");
if (maxNumber > 100000) throw new ArgumentException("Nur Zahlen kleiner oder gleich 100000 sind erlaubt");
//Liste anlegen
List<string> words = new List<string>();
//Alle Zahlen durchlaufen und prüfen
for (int curNumber = 1; curNumber <= maxNumber; curNumber++)
{
//Zahl durch n und m teilbar?
if ((curNumber % _n) == 0 && (curNumber % _m) == 0)
words.Add("FizzBuzz");
else if ((curNumber % _m) == 0)
words.Add("Buzz");
else if ((curNumber % _n) == 0)
words.Add("Fizz");
else
words.Add(curNumber.ToString());
}
//Ergebniss zurückliefern
return words;
}
}
Das ist natürlich nur eine von vielen Varianten einen FizzBuzz zu implementieren. Der Ansatz bei dieser Implementierung ist eine Klasse mit einer Funktion, die alle Varianten enthält. Versucht man eine Erweiterung mit beliebig vielen Fizz-Buzz-Zahlen und Wörtern, würde man die Zahl/Wort Kombination wahrscheinlich in einem Dicitonary
Delegaten als Parametrierung
Die Lösung, über die Werte zu Parametrieren reicht also nicht aus, da auch der Algorithmus und die Anzahl der Funkionen (Delegaten)variabel sein kann. Damit muss es eine Lösung geben, mit der ich Funktionen übergeben kann. Und von diesen Möglichkeiten gibt es in C# viele.
Schnittstellen
Der Klassiker ist eine Schnittstelle IFizzBuzz oder eine abstrakte Basisklasse mit unterschiedlichen Implementierungen für die verschiedenen Algorithmen. Mehr oder weniger parametrisierte Implementierungen dieser Schnittstelle werden an eine “Add” Methode des FizzBuzzers übergeben.
public class InterfaceFizzBuzzer
{
private List<IFizzBuzz> buzzList = null;
public InterfaceFizzBuzzer()
{
buzzList = new List<IFizzBuzz>();
}
public void Add(IFizzBuzz aBuzz)
{
buzzList.Add(aBuzz);
}
public IEnumerable<string> FizzBuzz(int maxNumber)
{
IList<string> result = new List<string>();
for (int i = 1; i <= maxNumber; i++)
{
// Result ermitteln
var numberResult = string.Empty;
foreach (var item in buzzList)
currentRes += item.GetForNumber(i);
// Ergebnis hinzufügen
if (string.IsNullOrEmpty(numberResult))
result.Add(i.ToString());
else
result.Add(numberResult);
}
return result;
}
Die Schnittstelle mit einer entsprechenden Implementierung sieht dann wie folgt aus:
public interface IFizzBuzz
{
string GetForNumber(int number);
}
class SimpleFizzBuzzImpl : IFizzBuzz
{
private int _baseNumber;
private string _word;
public SimpleFizzBuzzImpl(int baseNumber, string word)
{
_baseNumber = baseNumber;
_word = word;
}
public string GetForNumber(int number)
{
if (number % _baseNumber == 0) return _word;
return string.Empty;
}
}
Lamba Expressions
Lamba Expressions finde ich für kleinen Aufgaben genial. Man kann Logik an eine Funktion übergeben, ohne dafür entsprechende Schnittstellen, Delegaten oder abstrakte Basisklassen zu definieren. Der besondere Charme liegt dabei in der Übergabe in Logik, die nur einmal benutzt wird, wie bei “where” in LINQ. Die Lamba-Version des FizzBuzzer hat auch eine “Add” Methode, nimmt aber einen Parameter vom Typ Func<int, string> statt des Interfaces an.
public class LambdaFizzBuzzer
{
private List<Func<int, string>> buzzList = null;
public LambdaFizzBuzzer()
{
buzzList = new List<Func<int, string>>();
}
public void Add(Func<int, string> aBuzz)
{
buzzList.Add(aBuzz);
}
public IEnumerable<string> FizzBuzz(int maxNumber)
{
for (int i = 1; i <= maxNumber; i++)
{
var currentRes = string.Empty;
foreach (var item in buzzList)
currentRes += item.Invoke(i);
yield return string.IsNullOrEmpty(currentRes) ? i.ToString() : currentRes;
}
}
}
Die Verwendung des FizzBuzzer ist dann entsprechend mit Lambda-Expressions.
LambdaFizzBuzzer fb = new LambdaFizzBuzzer();
fb.Add(i => (i % 3 == 0) ? "Fizz" : string.Empty);
fb.Add(i => (i % 5 == 0) ? "Buzz" : string.Empty);
Fazit
Es gibt unterschiedliche Varianten, Code als Parameter an eine Methode zu übergeben. Interfaces und Lambda Expressions habe ich hier gezeigt. Delegaten und abstrakte Basisklasse sind hier zwar nicht vorgestellt, unterscheiden sich aber konzeptionell nicht großartig von Interfaces und Lambda Expressions. Der architekturelle Unterschied oder Frage ist, ob man den Code wiederverwenden möchte und die Algorithmen Teil der Architektur sind oder ob es “flüchtiger Code” wie Lambda Expressions sind.
