Micah and I took a look at my design to see where improvements could be made. I'm happy to say, we found several areas in which it could be improved, and some areas where things already seemed like they could work. The first thing we did was compare my design against S.O.L.I.D. which I will go over a little more later. Then we played around with a few strategies until we found one that seemed to fit best. The rest of the day I spent re-factoring my code, rewriting code using TDD, and trying to implement my new design.
What I learned:
Underlining Principles of being a Software Craftsman:
* Using a standard to test your design: One standard I typically used is SOLID, for Object Oriented Design. The acronym S.O.L.I.D. was coined by Uncle Bob at Object Mentor. It stands for:
Single Responsibility Principle: A class should hold one concept, with one reason to change
Open/Closed Principle: A class should be Open for extension, Closed for modification
Liskov Subsitution Principle: A child class must be able to substitute its parent class
Interface Segregation Principle: No client should be forced to depend on methods it doesn't use
Dependancy Inversion Principle: Always try to avoid dependancies on concrete classes
These design standards (not all of which apply for all languages, like ISP in Ruby) can lead you closer to a robust and flexible design. Or at least, they will help you stay away from rigid and fragile designs.
* Personal Code Review: It is overwhelmingly helpful if you look over your code a few days after you wrote it. When your purpose behind doing some little trick, or some conditional is no longer quite so clear, you are able to see some weaknesses in your code.
Its rather like when writing a paper, and then going over it right after you write it with the intention of making sure everything you said makes sense. This is typically ineffective because your intentions, with the words you used, are still clear to you, but if you give your paper to someone else, they can point out where your words might not match your interpretation or intent.
Writing code is very similar, even though your code may function as you intended, it might not read as easily as you thought it would. Or at lease, it is likely that you can do a little more refactoring to make your intentions pop out of the code so obviously that it only take one look to understand what you are getting at.
* If you can write a mock yourself, write the mock yourself: This one stems right from Micah, who believes that adding complex mocking frameworks, adds needless complexity. Instead, if you ever need a mock object, write it yourself.
This has several advantages, the most prominent of which is that once you have written a good MockClass, you can use it anytime, anywhere. For example, yesterday I was trying to mock out a FocusEvent in Java, and I needed to pass a Component derivative for the constructor. Well it turns out, Micah has already written convenient mocks that I could use. Other advantages are that, you always know what your mocks can and can't do, and that you can keep it as simple and lightweight as possible.
General Knowledge of Language:
* Parallel inheritance structures can be dangerous: They might start out pretty, and you can add some nice abstract classes or interfaces to help segregate them, but eventually decay is inevitable... That is, assuming they continue to grow. There will be a point where if you want to make an addition to one side of the structure, that addition must be able to handle all the cases of the other side. This isn't too bad with just two or three derived classes, but thou shalt not make four. Five is right out.
* Just a note on abstract classes. A class is abstract if it has at least one abstract method. Though, you can declare a class as abstract even without an abstract method, thats just a phony.
* Strategy Pattern (aka Policy Pattern): The Strategy Pattern is way to use a variety of different algorithms for a single context at run time. Say you wanted to make a simple calculator that could add, subtract, and multiply. You could use the Strategy Pattern, with the calculator interaction with your user as the context, and the three operations as your strategies.
In Java, you would create a Calculator class, which had a means to interact with the user, and which would contain a reference to a Strategy Interface. The Strategy Interface would have an calculate method, and three derived classes which would implement this method. Then in the context, or the Calculator class, you would pass any of the operation strategies as an argument of the Calculator constructor, and then call calculate on the strategy reference to use the desired operation.
Now to finish implementing my KeyProcessor using the dispatcher!