In my previous post on the subject, I showed how you can remove some of the friction caused by applying the State pattern. In fact, as some of you rightfully pointed out, this friction isn?t caused by the State pattern itself but by a violation of the Interface Segregation Principle. The use of role interfaces somewhat mitigated this violation but caused some type checking and casting in the context class (named Account in our example).
I just wanted to put out a slight variation of our solution that defers this kind of type checking until runtime. In other words, we?re going to apply some duck typing in order to come up with some improvements.
But hang on, isn?t C# supposed to be a static language and isn?t duck typing an exclusive feature of dynamic languages like Ruby or Python? Heck no! One can have duck typing in a static language as well. A good example of this is the Boo programming language which is a static language but provides the ability to give up this safety net. But I do believe that dynamic languages like Ruby or Python provide a more supple syntax for supplying duck typing capabilities while a static language usually requires a bit more ceremony.
The latest C# 4.0 compiler delivers some dynamic language features using the dynamic keyword which in turn accomplishes it?s functionality by leveraging the DynamicObject class (which seems to live in the System.Dynamic namespace). I have to admit that I didn?t spent much time on this yet.
But while the rest of the world is still on .NET 3.5 or earlier versions of the .NET framework, we don?t have to wait until the next upgrade to use some of the dynamic language features like duck typing, mixins and the like.
Let me first show you the code of the Account class from the previous post that demonstrates the type checking and casting to a role interface:
public void Withdraw(double amount) { // One can not always withdraw if(false == CanWithdraw()) { // Should throw an exception or at least a towel Console.WriteLine("No funds available for withdrawal!"); return; } var canWithdraw = (ICanWithdraw)State; canWithdraw.Withdraw(amount); ... } private Boolean CanWithdraw() { return State is ICanWithdraw; }
What I would like to do is to remove the CanWithdraw check entirely without running into a NullReferenceException when a particular state doesn?t support withdrawing money. But first, I want to slightly decouple the Account class from the SilverState, GoldState and RedState classes by introducing an interface named IAccountStateChanger.
public interface IAccountStateChanger { void InstituteStateTo(State newState); }
This is now used by the state classes to change the state of the Account class without using a property setter in order to get rid of the false sense of encapsulation. The implementation of this interface by the Account class is where the fun begins.
void IAccountStateChanger.InstituteStateTo(State newState) { _state = newState; _dynamicState = new DynamicObject(_state) + _missingStateMethodHandler; }
Besides having a state we also introduced a dynamic state which is simply an instance of Linfu?s DynamicObject class. I?ll talk about the missing state method handler part shortly, but first let?s look what we can do with our dynamic state object.
public void Withdraw(Double amount) { var canWithdraw = _dynamicState.CreateDuck<ICanWithdraw>(); canWithdraw.Withdraw(amount); ... }
Notice that we got rid of the type checking part. But what if a particular state doesn?t support withdrawing money? Here?s were the method missing piece of the puzzle falls in. Linfu provides an interface named IMethodMissingCallback which lets you decide what to do when the requested method call cannot be resolved.
public class MissingStateMethodHandler : IMethodMissingCallback { public void MethodMissing(Object source, MethodMissingParameters missingParameters) { if(typeof(ICanWithdraw).GetMethods()[0].Name == missingParameters.MethodName) { Console.WriteLine("No funds available for withdrawal!"); // Let Linfu know we're on top of things missingParameters.Handled = true; } } public Boolean CanHandle(MethodInfo method) { return false; } }
You can do pretty much anything in here like delegating the call to another method, but for demonstration purposes we just write a message to console. As shown earlier we literally add an instance of this handler to the dynamic object and we?re all set up. Using this approach yields the same results but without the type checking.
I?d like to mention that I?ve been using the DynamicObject class from Linfu 1.0 which you can download here. For some reason this class hasn?t been included in the 2.0 release and higher which is a bit sad because I really like some of the possibilities it has to offer. I?ll cover some of these in one of my next posts.
2 thoughts on “Don’t Give Up on the State Pattern Just Yet – Revisited”
Comments are closed.