Choose interface or base class

Posted on 2011-05-30 14:20  nealgaga  阅读(258)  评论(0)    收藏  举报

I often hear the question, “Should I design a base type or an interface?” The answer isn’t
always clear-cut. Here are some guidelines that might help you:
n IS-A vs. CAN-DO relationship A type can inherit only one implementation. If the
derived type can’t claim an IS-A relationship with the base type, don’t use a base type;
use an interface. Interfaces imply a CAN-DO relationship. If the CAN-DO functionality
appears to belong with various object types, use an interface. For example, a type
can convert instances of itself to another type (IConvertible), a type can serialize an
instance of itself (ISerializable), etc. Note that value types must be derived from
System.ValueType, and therefore, they cannot be derived from an arbitrary base class.
In this case, you must use a CAN-DO relationship and define an interface.
n Ease of use It’s generally easier for you as a developer to define a new type derived
from a base type than to implement all of the methods of an interface. The base type
can provide a lot of functionality, so the derived type probably needs only relatively
small modifications to its behavior. If you supply an interface, the new type must
implement all of the members.
n Consistent implementation No matter how well an interface contract is documented,
it’s very unlikely that everyone will implement the contract 100 percent correctly. In
fact, COM suffers from this very problem, which is why some COM objects work correctly
only with Microsoft Office Word or with Windows Internet Explorer. By providing
a base type with a good default implementation, you start off using a type that works
and is well tested; you can then modify parts that need modification.
n Versioning If you add a method to the base type, the derived type inherits the new
method, you start off using a type that works, and the user’s source code doesn’t even
have to be recompiled. Adding a new member to an interface forces the inheritor of
the interface to change its source code and recompile.
In the FCL, the classes related to streaming data use an implementation inheritance design.
The System.IO.Stream class is the abstract base class. It provides a bunch of methods, such
as Read and Write. Other classes—System.IO.FileStream, System.IO.MemoryStream,
and System.Net.Sockets.NetworkStream—are derived from Stream. Microsoft chose an
326 Part II Designing Types
IS-A relationship between each of these three classes and the Stream class because it made
implementing the concrete classes easier. For example, the derived classes need to implement
only synchronous I/O operations; they inherit the ability to perform asynchronous I/O operations
from the Stream base class.
Admittedly, choosing to use inheritance for the stream classes isn’t entirely clear-cut; the
Stream base class actually provides very little implementation. However, if you consider the
Microsoft Windows Forms control classes, in which Button, CheckBox, ListBox, and all of
the other controls are derived from System.Windows.Forms.Control, it’s easy to imagine
all of the code that Control implements, which the various control classes simply inherit to
function correctly.
By contrast, Microsoft designed the FCL collections to be interface based. The
System.Collections.Generic namespace defines several collection-related interfaces:
IEnumerable<out T>, ICollection<T>, IList<T>, and IDictionary<TKey, TValue>.
Then Microsoft provided a number of classes, such as List<T>, Dictionary<TKey, TValue>,
Queue<T>, Stack<T>, and so on, that implement combinations of these interfaces. Here the
designers chose a CAN-DO relationship between the classes and the interfaces because the
implementations of these various collection classes are radically different from one another.
In other words, there isn’t a lot of sharable code between a List<T>, a Dictionary<TKey,
TValue>, and a Queue<T>.
The operations these collection classes offer are, nevertheless, pretty consistent. For example,
they all maintain a set of elements that can be enumerated, and they all allow adding and
removing of elements. If you have a reference to an object whose type implements the
IList<T> interface, you can write code to insert elements, remove elements, and search for
an element without having to know exactly what type of collection you’re working with. This
is a very powerful mechanism.

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3