Introduction
While coding at work (Autoscribe), I needed to come up with a cross-cutting observer pattern. When I say 'cross-cutting', I mean that the mechanism had to work with several class types using interfaces.
What was needed, was an easy way to implement the interface in classes that have no base-type, or in classes that already have a base type. It also had to be relatively fool-proof to code with. I decide to present this as a pattern as this looks more imposing than an article entitled 'A Good Trick with Interfaces'.
Although I have presented this technique in C#, it would work just as well in any language that disallows multiple inheritance such as VB.NET and Java.
Background
The 'Internal Implementor' (for want of a better name) is a quick trick to reduce the amount of code required to implement the same interface across classes. It consists of an interface definition, a class that implements the basics of the interface and a procedure to using that class in derived and non-derived classes. A derived class in this case is a class that already inherits from some other base class. The normal approach is to inherit the interface and then either implement the interface again in the derived class or wrap an internal implementation instance al la Visual Basic inheritance.
The pattern consists of the Interface class (IMyInterface
in our example) and an implementation class (MyInterfaceImpl
). The interface consists of two methods: MyBasicMethod
is a method that is the same for all class implementations. This is very simple to code in the MyInterfaceImpl
class. The second method, MyAbstractMethod
, will be implemented differently in each class type. Traditionally, this would be created using the abstract
keyword. However, this means that until the method is implemented, classes cannot be instantiated. Therefore, in this case, we don't use the abstract
keyword. Instead, we implement the MyAbstractMethod
in the MyInterfaceImpl
as a method that simply throws an exception, indicating that this method should be overridden.
This allows us to use the MyInterfaceImpl
class as both a base class (where abstract methods would be valid) and as an internal implementation class (where abstract methods would not be valid).
Example
The example code shows the basic interface:
public interface IMyInterface
{
void MyBasicMethod();
void MyAbstractMethod();
}
The MyInterfaceImpl
class implements the basic functionality of this interface, allowing our non-derived class to inherit directly from it, overriding the class-specific method:
public class MyNonDerivedClass : Internal.MyInterfaceImpl
{
public override void MyAbstractMethod()
{
Console.WriteLine("MyAbstractMethod called from type {0}",
this.GetType().Name);
}
}
If the MyInterfaceImpl.MyAbstractMethod
was truly an abstract method, this would make no difference. However, to enable the same class to be useable as a contained implementation, the MyInterfaceImpl
class uses another technique:
public void MyBasicMethod()
{
Console.WriteLine("Interface implementation");
}
public virtual void MyAbstractMethod()
{
throw new System.ApplicationException("Override this method, you fool!");
}
The important point here is that the abstract method is implemented to simply throw an exception. If this was made an abstract method, then you would be unable to instantiate an instance to allow wrapping of this class as a contained implementation, and if the method simply returned without doing anything, the developer might not notice that it had not been overwritten. In this method, an exception would be thrown at runtime if an override is not provided.
The following code shows how the IMyInterface
inheritance and wrapping a contained MyInterfaceImpl
work in the MyDerivedClass
type.
public class MyDerivedClass : SomeBaseClass, IMyInterface
{
private Internal.MyInterfaceImpl internalImplementation = null;
public MyDerivedClass()
{
internalImplementation = new Internal.MyInterfaceImpl();
}
#region IMyInterface Members
public void MyBasicMethod()
{
internalImplementation.MyBasicMethod();
}
public void MyAbstractMethod()
{
Console.WriteLine("MyAbstractMethod called from type {0}",
this.GetType().Name);
}
#endregion
}
The class-specific code for MyAbstractMethod
is implemented explicitly instead of passing the call off to the contained MyInterfaceImpl
class. The BadDerivedClass
does this in the code example to demonstrate the exception being thrown:
public void MyAbstractMethod()
{
internalImplementation.MyAbstractMethod();
}
This method allows the use of a single class as both an inheritance base and a containment class. Without this technique, two MyInterfaceImpl
-style classes would be required: one that provided an abstract method that required overriding, and another that provided a NULL
method implementation for containment wrapping. I find this pattern is quick to implement in cases where complex framework classes share subsets of functionality.
Pros and Cons
The benefit of abstract methods is that the compiler catches them if they are not overwritten. This pattern does not have compile-time checking, but uses runtime exceptions to do the same job. This is less convenient in the short term as errors are not detected until testing is performed.
On the other hand, this method is simple and reduces the need for two implementation classes, so in the long run (if you have many cross-cutting functionality subsets, or many classes that must share functionality), then the reduced work more than makes up for the lack of compile-time error support.
Comments Welcome
If you know better than I do about any of this, comments are welcome. There are probably spelling and grammar mistakes in there somewhere, and if you're bored, then feel free to look for them.
Acknowledgements
I would like to thank my employer, John Boother at Autoscribe for allowing me to write this article based on research performed for my job.
History
15th April 2004 -- Ooh, look! My second CodeProject article. People might start to think I know what I'm doing.
After ten years studying biology and evolution, I threw it all away and became a fully paid up professional programmer. Since 1990 I have done C/C++ Windows 3.1 API, Pascal, Win32, MFC, OOP, C#, etc, etc.
PS. In the picture, I'm the one in blue. On the right.