This is a quite common interview question. Many a time candidates
escape this question by giving the definition of an interface. “Interface is a
contract which mandates implementation of the contractual methods”. Some also
say “Interface is the way by which we can realize multiple inheritances”.
The above said definitions and explanation stands true; but
interfaces are not just that. It plays a major role in design of systems. Let us see what is it capable of.
Interface is a contract
This statement is directly taken
from the above mentioned definition. But the question to be asked is “Why
should we abide to that contract?” or “Why should we implement an interface.
Let us take an example. We are familiar with the interface “IEnumerable”.
We also know that this interface needs to be implemented if a class has to
qualify as a custom collection or collection iteratable by “foreach” statement.
Thus the contract is enforced by the “foreach” statement and if we have to use
foreach against our custom collection; we have to implement IEnumerable.
By this the “foreach” is acting against abstracts and it
would look for the methods/properties in the candidate class to go ahead with
the “iterate” operation. Thus other functionalities of the custom class is
really not a concern for “foreach”.
As an example let us build a simple validation engine. Let
us assume that the validation engine runs on multiple rules
The validation Engine will perform the following operations on
the rules
- Execute validation rules in particular order
- Collate all failures and throw error.
Let us define the validation rule contract as follows
public interface IValidationRule
{
short Order { get; }
string ErrorMessage { get; }
bool Validate(Entity subject);
}
And Validation Engine as
public class ValidationEngine
{
private readonly IEnumerable<IValidationRule> _validationRules;
public ValidationEngine(IEnumerable<IValidationRule> validationRules)
{
_validationRules = validationRules;
}
public void Validate(Entity subject)
{
var failedMessages = new List<string>();
foreach (var rule in _validationRules.OrderBy(ru => ru.Order))
{
var validationResult = rule.Validate(subject);
if (validationResult) continue;
failedMessages.Add(rule.ErrorMessage);
}
if (failedMessages.Any())
{
throw new ValidationFailedException(failedMessages);
}
}
}
Validation engine expects the rules to be injected as
collections and on invoking the Validate method with the “Entity”payload, it
iterates through all rules and executes each of them. On failure the message is
added to a collection. In the end of iteration, if there are any failure an
exception is thrown.
Entity (Validation payload) defined as
public class Entity
{
public string Property1 { get; set; }
public int NumericProperty { get; set; }
}
Let us define the exception as .
public class ValidationFailedException:Exception
{
public ValidationFailedException( IList<string> failedRules)
{
Failures = failedRules;
}
public IList<string> Failures { get; private set; }
public override string Message
{
get
{
return "Validation failed. See Failures property for details";
}
}
}
Sample rules as
1. A rule to validate Property1
public class Property1RequiredRule :IValidationRule
{
public Property1RequiredRule()
{
Order = 1;
ErrorMessage = "Property1 is a required field";
}
public short Order
{
get;
private set;
}
public string ErrorMessage
{
get;
private set;
}
public bool Validate(Entity subject)
{
return !string.IsNullOrEmpty(subject.Property1);
}
}
2. A rule to validate NumericProperty
public class NumericPropertyShouldBePostiveRule:IValidationRule
{
public NumericPropertyShouldBePostiveRule()
{
Order = 2;
ErrorMessage = "NumericProperty should be positive";
}
public short Order
{
get;
private set;
}
public string ErrorMessage
{
get;
private set;
}
public bool Validate(Entity subject)
{
return subject.NumericProperty >= 0;
}
}
And we shall invoke the engine as
class Program
{
static void Main(string[] args)
{
var validationRules = new List<IValidationRule> { new Property1RequiredRule(), new NumericPropertyShouldBePostiveRule() };
var subjectEntity = new Entity { NumericProperty = -3 };
try
{
var validationEngine = new ValidationEngine(validationRules);
validationEngine.Validate(subjectEntity);
}
catch (ValidationFailedException ex)
{
Console.WriteLine(ex.Message);
foreach (var failureMessage in ex.Failures)
{
Console.WriteLine(failureMessage);
}
}
Console.Read();
}
}
Validation engine works against abstracts which implements IValidationRule.
Validation engine or the validation altogether does not change for adding or
altering rules. Adding a new rule is as simple as create a new class and make
it implement IValidationRule and add the new class to the engine. Thus the
validation engine is extensible.
Multiple Inheritances is supported by interfaces.
Let us change the validation engine a bit. We would ask the
engine to execute some action before and after validation. There will be only
certain rules which would requires either post or pre operations. Some rule may
require both these actions implemented.
Let us call these methods as PreAction and PostAction. We
cannot add these methods to IValidationRule because only certain rules need
these operations. We shall segregate these methods into two different contracts
viz; IPreActionRule and IPostActionRule.
public interface IPreActionRule
{
void Action();
}
public interface IPostActionRule
{
void Action();
}
Validation engine after introducing pre and post action
public class ValidationEngine
{
private readonly IEnumerable _validationRules;
public ValidationEngine(IEnumerable validationRules)
{
_validationRules = validationRules;
}
public void Validate(Entity subject)
{
var failedMessages = new List<string>();
foreach (var rule in _validationRules.OrderBy(ru => ru.Order))
{
var preActionable = rule as IPreActionRule;
if (null != preActionable) preActionable.Action();
var validationResult = rule.Validate(subject);
var postActionable = rule as IPostActionRule;
if (null != postActionable) postActionable.Action();
if (validationResult) continue;
failedMessages.Add(rule.ErrorMessage);
}
if (failedMessages.Any())
{
throw new ValidationFailedException(failedMessages);
}
}
}
Every rule has to implement IValidationRule and optionally
if the rule has to operate Pre/Post action then those rules has to implement
IPreActionRule/IPostActionRule.
Let us modify the rules to accommodate pre and post
operation.
1. Property1RequiredRule requires only post operation to be performed
public class Property1RequiredRule :IValidationRule,IPostActionRule
{
public Property1RequiredRule()
{
Order = 1;
ErrorMessage = "Property1 is a required field";
}
public short Order
{
get;
private set;
}
public string ErrorMessage
{
get;
private set;
}
public bool Validate(Entity subject)
{
Console.WriteLine("Validating Property1RequiredRule");
return !string.IsNullOrEmpty(subject.Property1);
}
public void PostAction()
{
Console.WriteLine("Executing Post Action within Property1RequiredRule");
}
}
2. NumericPropertyShouldBePositiveRule requires only pre-action
public class NumericPropertyShouldBePostiveRule:IValidationRule,IPreActionRule
{
public NumericPropertyShouldBePostiveRule()
{
Order = 2;
ErrorMessage = "NumericProperty should be positive";
}
public short Order
{
get;
private set;
}
public string ErrorMessage
{
get;
private set;
}
public bool Validate(Entity subject)
{
Console.WriteLine("Validating NumericPropertyShouldBePostiveRule");
return subject.NumericProperty >= 0;
}
public void PreAction()
{
Console.WriteLine("Executing PreAction with in NumericPropertyShouldBePositiveRule");
}
}
In the end Validation engine uses contracts IValidationRule,
IPreActionRule and IPostActionRule for validation. Who ever want to use
validation engine should abide to those above mentioned contracts.
Hope the post helps in understanding how interfaces are used
to program against abstracts and the role it plays in building frameworks.