So I was having lunch with @jeffreypalermo, @dannydouglass, and my other teammates, and they were chatting about interview processes/techniques.
Jeffrey was in town for our annual HeadSpring training and mentioned the infamous fizz buzz problem and I couldn’t but think about
how I’d implement it...cleanly!! One given is that whomever reads it does not want to see a
proliferation of if statements. But they have to get written at some point,
right? So thinking about modern practices and design principles, I thought to myself…why
not let SOLID guide me here? And that’s exactly what I did.
So there are several components at play here; the
FizzBuzzCommandProcessor and the FizzBuzzCommandRetriever. I took the processor
technique from Jeffrey when he mentioned how they implemented the rules engine.
I thought it was pretty cool and would be fun to implement something similar on
my own. And with the advent of CQRS, I’m just really hooked on commands in
general. They promote such strong usage of Single Responsibility Principle (SRP). The
command processor takes input from the command retriever, and matches up each
command with the value that it can handle. That’s it.
using System.Collections.Generic; namespace ADubb.FizzBuzz { public interface IFizzBuzzCommandProcessor { void Process(IEnumerable<int> numbers); void Process(int number); } }
using System.Collections.Generic; using System.Linq; namespace ADubb.FizzBuzz { public class FizzBuzzCommandProcessor : IFizzBuzzCommandProcessor { static readonly IEnumerable<IFizzBuzzHandler> HandlerCache; static readonly FizzBuzzCommandRetriever CommandRetriever; static FizzBuzzCommandProcessor() { CommandRetriever = new FizzBuzzCommandRetriever(); HandlerCache = CommandRetriever.GetHandlers().ToList(); } public void Process(IEnumerable<int> numbers) { numbers.ToList().ForEach(Process); } public void Process(int number) { var handlers = HandlerCache.Where(h => h.CanHandle(number)); handlers.ToList().ForEach(h => h.Handle(number)); } } }
Next there is the FizzBuzzCommandRetriever. I decided to keep things simple and only scan the currently executing receiver for command handlers as opposed to the entire AppDomain. This guy helps
me separate the responsibility of finding finding IFizzBuzzCommandHandlers at
runtime. That’s all it knows how to do. We've achieved a clean separation between the
what and the how.
using System.Collections.Generic; namespace ADubb.FizzBuzz { public interface IFizzBuzzCommandRetriever { IEnumerable<IFizzBuzzHandler> GetHandlers(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace ADubb.FizzBuzz { public class FizzBuzzCommandRetriever : IFizzBuzzCommandRetriever { public IEnumerable<IFizzBuzzHandler> GetHandlers() { var handlers = Assembly.GetExecutingAssembly().GetExportedTypes().Where( t => typeof(IFizzBuzzHandler).IsAssignableFrom(t) && t.IsClass) .Select(Activator.CreateInstance) .Cast<IFizzBuzzHandler>(); return handlers; } } }
Then I have actual implementations of IFizzBuzzCommandHandlers.
These are consumed by the processor and told to act on their respective inputs.
Clear as day.
namespace ADubb.FizzBuzz { public interface ICommandHandler<in TType> { bool CanHandle(TType target); void Handle(TType target); } }
namespace ADubb.FizzBuzz { public interface IFizzBuzzHandler : ICommandHandler<int> { } }
using System; namespace ADubb.FizzBuzz { public class MultiplesOfThreeFizzBuzzHanlder : IFizzBuzzHandler { public bool CanHandle(int target) { return target % 3 == 0; } public void Handle(int target) { Console.WriteLine("{0} is a multiple of 3.", target); } } }
using System; namespace ADubb.FizzBuzz { public class MultiplesOfTwoFizzBuzzHanlder : IFizzBuzzHandler { public bool CanHandle(int target) { return target % 2 == 0; } public void Handle(int target) { Console.WriteLine("{0} is even.", target); } } }
Lastly we have the Open Closed Principle (OCP) that comes at
play. I was telling Jeffrey how the template pattern lends itself very suitable
to the OCP. Since we have the command retriever, it’s easy for us to plug additional
handlers into the pipeline. It’s also just as easy to take them out. What I
admire most is that the mere act of adding or removing a class from the assembly
has no adverse affect on the runtime. See for yourself. Just comment out the
class definition for one of the handlers and observe as its output never gets
logged to the console. Our template comes into play with inheritance of course.
There is an IFizzBuzzHandler that each handler implements. It’s merely just a
marker interface. This same concept is applied with handlers in NServiceBus. I
like J
Program...
using System.Linq; namespace ADubb.FizzBuzz { class Program { static void Main() { var oneToOneHundred = Enumerable.Range(1, 101); IFizzBuzzCommandProcessor fizzBuzzCommandProccessor = new FizzBuzzCommandProcessor(); fizzBuzzCommandProccessor.Process(oneToOneHundred); } } }Output...
Overall, it was nice to watch all the constructs come together and play nicely with one another. Each component has a single responsibility. Nice fine grained, granular classes that don’t know how to do too much. They specialize at a specific task. They know how to do one thing really, really well. And that’s it.
No comments:
Post a Comment