Recipe 3.17 Polymorphism via Interfaces
Problem
You need to implement
polymorphic functionality on a set of existing classes. These classes
already inherit from a base class (other than
Object), thus preventing the addition of
polymorphic functionality through an abstract or concrete base class.
In a second situation, you need to add polymorphic functionality to a
structure. Abstract or concrete classes cannot be used to add
polymorphic functionality to a structure.
Solution
Implement polymorphism using an interface instead of an abstract or
concrete base class. The code shown here defines two different
classes that inherit from ArrayList:
public class InventoryItems : ArrayList
{
// ...
}
public class Personnel : ArrayList
{
// ...
}
We want to add the ability to print from either of these two objects
polymorphically. To do this, an interface called
IPrint is added to define a
Print method to be implemented in a class:
public interface IPrint
{
void Print( );
}
Implementing the IPrint interface on the
InventoryItems and Personnel
classes gives us the following code:
public class InventoryItems : ArrayList, IPrint
{
public void Print( )
{
foreach (object obj in this)
{
Console.WriteLine("Inventory Item: " + obj);
}
}
}
public class Personnel : ArrayList, IPrint
{
public void Print( )
{
foreach (object obj in this)
{
Console.WriteLine("Person: " + obj);
}
}
}
The following two methods TestIPrintInterface and
CommonPrintMethod show how any object that
implements the IPrint interface can be passed to
the CommonPrintMethod polymorphically and printed:
public void TestIPrintInterface( )
{
// Create an InventoryItems object and populate it
IPrint obj = new InventoryItems( );
((InventoryItems)obj).Add("Item1");
((InventoryItems)obj).Add("Item2");
// Print this object
CommonPrintMethod(obj);
Console.WriteLine( );
// Create a Personnel object and populate it
obj = new Personnel( );
((Personnel)obj).Add("Person1");
((Personnel)obj).Add("Person2");
// Print this object
CommonPrintMethod(obj);
}
private void CommonPrintMethod(IPrint obj)
{
Console.WriteLine(obj.ToString( ));
obj.Print( );
}
The output of these methods is shown here:
InventoryItems
Inventory Item: Item1
Inventory Item: Item2
Personnel
Person: Person1
Person: Person2
Discussion
The use of interfaces is found throughout the Framework Class Library
(FCL). One example is the IComparer interface:
this interface requires a class to implement the
Compare method, which compares two objects to
determine if one is greater than, less than, or equal to another
object. This method is used by the Array.Sort and
Array.BinarySearch static methods to allow sorting
and searching to be performed on the elements contained in an array.
For example, if an array contained objects that implemented a custom
IComparer interface, the static
Sort and BinarySearch methods
would use this interface to customize its sorting/searching of
elements in that array.
Another example is found in the IEnumerable and
IEnumerator interfaces. These interfaces let you
iterate over items in a container using the
foreach loop. It does not matter what the
contained items are or what the containing object is. The
foreach loop can simply use these interfaces
regardless of the type of objects that implement them.
In many cases, you will choose to implement polymorphism through
abstract base classes; however, there are some cases where interfaces
are superior. Interfaces should be considered before abstract base
classes in the following cases:
When several unrelated classes need to implement a common subset of
their functionality polymorphically. The Solution to this recipe
demonstrates this concept. If one or more of the classes already inherits from a base class, an
interface may be added to implement polymorphism. If you look at the
Solution for this recipe, you'll see that our
InventoryItem class could have inherited from an
existing Item class. This would make it impossible
to use an abstract base class. An interface can be added in this case
to implement polymorphism. If, in future versions of your data type, you will want to add new
polymorphic functionality without breaking the existing interface of
your data type. Interface polymorphism provides better versioning
than abstract or concrete base classes. To add new polymorphic
functionality, implement a new interface containing this
functionality on your existing data type. When you need to implement polymorphism on value types.
Implementing polymorphism through interfaces works not only on
reference types, but also with value types. Value types cannot derive
from any other type except ValueType; this makes
them unable to implement an abstract base class. We must instead use
interfaces to implement polymorphism. This can be shown by changing
the following class declarations:
public class InventoryItems : ArrayList
public class Personnel : ArrayList
to this:
public struct InventoryItems : ArrayList, IPrint
public struct Personnel : ArrayList, IPrint
These structures now can act polymorphically on the
IPrint interface. When implementing an interface
on a structure, be aware that a boxing operation will be performed
whenever the value is cast to the interface type (in this case, the
IPrint interface). The boxed object is a copy of
the original structure. This means that if you modify the boxed
object, using a reference to the interface, you will be modifying a
copy of the original structure.
See Also
See the "interface" keyword in the
MSDN documentation.
|