I've recently built an online hotel booking interface that integrates with a third party booking system.
Since this third party system will be most likely used for future projects I tried to design each component as generic as possible so that they can be reused.
The specification required a series of validation rules to be applied to user input. These rule varied from maximum length of stay to how far ahead in time a booking can be made and I was sure they would have changed with time and will definitely change when we integrated the same system for other clients.
I implemented them as separate modular layers on top of the basic functionality. For maintainability and modularity I wanted to avoid to implement them as as series of nested if statements which are harder to maintain.
What I wanted to achieve was:
1) Implement one or more rules independently
2) Rules should be reusable for other projects
3) If a rule fails the workflow should handle the notification to the user interface
4) Client side (Javascript) validation rules and Server side (ASP.NET) validation rules should be expressed in one single place.
5) Code structure should be simple to understand and quick to grasp for fellow programmers.
6) Extract a generic and reusable library to accomplish similar validation.
One of these patterns is the
Decorator pattern, which is used to dynamically extend (decorate) the functionality of an existing object.
In fact the Decorator pattern uses polymorphism to extend the functionality of an existing object allowing to stack several decorator classes on top of each other, each time adding a new functionality to the overridden method(s).
I decided that this pattern was a good candidate:
The user interface component that holds the form fields(in this case a .NET UserControl) will be responsible to collect the user inputted data from the form collection and initialise a UserInput object.
The UserInput class would have the followings:
- bool IsValid();
- void Validate();
- List Errors { get; set;} //Error is a simple object extending the System.Exception object
The UserInput object would be responsible for performing the basic data validation which is checking that the inputted data can be parsed from String to its final type ( ie DateTime, int etc)
The UserInputDecorator decorator abstract class would implement the UserInput interface and expose the followings:
- UserInput _UserInput{get; private set;}
- UserInputDecorator(UserInput ui) { this._UserInput = ui; }
- abstract bool IsValid();
- abstract void Validate();
- List Errors { get; set;} //Error is a simple object extending the System.Exception object
Now a series of classes can be derived from the UserInputDecorator to add validation functionality to the base class.
These classes are for example:
- UserInputDatesAreSequential //Asserts that the start date is prior to the end date
- UserInputMaxStayLength //Asserts that the max stay is less than a client defined one
-UserInputMaxAheadBooking // Asserts that the booking is no more than certain days in future
- UserInputValidHotelCode // Asserts that the HotelCode is valid
Using the Decorator pattern I could then stack up the various validations in the following way:
UserInput u = new UserInput(start_date, end_date, hotel_code);
u = new UserInputDatesAreInRightOrder(u);
u = new UserInputMaxStayLength(u);
u = new UserInputMaxAheadBooking(u);
u = new UserInputValidHotelCode(u);
u.Validate();
if(!u.isValid())
{
//Extract list of errors and display them to the user
}
This way I was able to extract the rules and make them generic validation rule.
The combination of these rules creates a client specific set of rule.
I can run Unit Testing because each Rule is isolated and does not depend on the user interface's User Control.
The client side validation was accomplished by wrapping these rules with a web service that the Javascript queries when rules need to be enforced within the browser.
This seemed to me a neat way to accomplish rules validation. I'm not a big fan of nested if statements because they are hard to maintain as their number increase.
Instead with this approach the code remains simple and readable, unit testable, modular and reusable.
Thanks for reading.