Applying the Open Closed Principle in Silverlight and WPF using MEF
Posted on | June 29, 2010 | 2 Comments
In this post I want to show how MEF can be used to apply the Open Closed Principle where a class is open for extension but closed for modification.
In the Calculator application we have been building as part of the Silverlight refactoring series we could have used the code below to validate a users input.
By the way we are throwing exceptions because the application is using ValidatesOnExceptions.
public class CalculatorValidator
{
public void ValidateNumber(string value)
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Please enter a number");
}
int number;
if (!int.TryParse(value, out number))
{
throw new Exception("That's not a number");
}
}
}
If we wanted to add more validation rules to check the value cannot be negative or greater than a hundred we have to modify the CalculatorValidator class with two additional if statements.
public class CalculatorValidator
{
public void ValidateNumber(string value)
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Please enter a number");
}
int number;
if (!int.TryParse(value, out number))
{
throw new Exception("That's not a number");
}
if (number < 0)
{
throw new Exception("Number cannot be negative");
}
if (number > 100)
{
throw new Exception("That number is too big!");
}
}
}
and this pattern would continue every time you added a new rule, leaving the code as maintainable and stable as a wobbly tower!
Implementing the Open Closed Principle
Lets start by creating an interface for a validation rule
public interface ICalculatorValidationRule
{
bool IsValid(string number);
string ErrorMessage { get; }
}
Next we create a class for each validation rule by implementing the ICalculatorValidationRule interface
[Export(typeof(ICalculatorValidationRule))]
public class ValidateValueIsNotNullOrEmpty : ICalculatorValidationRule
{
public bool IsValid(string value)
{
return !String.IsNullOrEmpty(value);
}
public string ErrorMessage
{
get
{
return "Please enter a number";
}
}
}
[Export(typeof(ICalculatorValidationRule))]
public class ValidateValueIsANumber : ICalculatorValidationRule
{
public bool IsValid(string value)
{
if (!String.IsNullOrEmpty(value))
{
int number;
return int.TryParse(value, out number);
}
return true;
}
public string ErrorMessage
{
get { return "That's not a number"; }
}
}
[Export(typeof(ICalculatorValidationRule))]
public class ValidateValueIsNotNegative : ICalculatorValidationRule
{
public bool IsValid(string value)
{
int number;
if (int.TryParse(value, out number) && number < 0)
{
return false;
}
return true;
}
public string ErrorMessage
{
get { return "Number cannot be negative"; }
}
}
[Export(typeof(ICalculatorValidationRule))]
public class ValidateValueIsLessThanHundred : ICalculatorValidationRule
{
public bool IsValid(string value)
{
int number;
if (int.TryParse(value, out number) && number > 100)
{
return false;
}
return true;
}
public string ErrorMessage
{
get { return "That number is too big!"; }
}
}
An alternative to parsing the value to an integer in the validation rules to check the range would be to order how MEF composes the validation rules and ensure rule to validate the value is an integer is done earlier.
More information about this can be found in answer to this question on Stack Overflow – How does MEF determine the order of its imports?
ImportMany example in MEF
By using the ImportMany attribute on a collection in MEF we can iterate over all of the parts which export the ICalculatorValidatorInterface and check the value passes the validation rule by calling the IsValid method.
If the value is not valid a exception is thrown with the relevant error message.
The code below shows how the CalculatorValidator class would look
public class CalculatorValidator
{
[ImportMany]
public IEnumerable<ICalculatorValidationRule> CalculatorValidationRules { get; set; }
public void ValidateNumber(string value)
{
foreach (var calculatorValidationRule in CalculatorValidationRules)
{
if (!calculatorValidationRule.IsValid(value))
{
throw new Exception(calculatorValidationRule.ErrorMessage);
}
}
}
}
Couldn’t have done it without the extensibility of MEF
If we wanted to add a new validation rule, all we have to do is create another class which implements the ICalculatorValidationRule interface, add an export attribute and MEF will do the rest.
The CalculatorValidator class is now open to extension but closed for modification
Comments
2 Responses to “Applying the Open Closed Principle in Silverlight and WPF using MEF”
Leave a Reply
July 6th, 2010 @ 2:06 pm
Good points well made.
Having each validation rule in its own class also means that unit tests only have one concern and therefore easier to write, maintain etc.
July 25th, 2010 @ 8:27 pm
[...] Jag Reehal’s article Applying the Open Closed Principle In Silverlight and WPF Using MEF in his excellent blog Arrange Act [...]