Separation of interface from implementation is a core principle of component-oriented programming.
In component-based programming, the basic unit of use in an application is a binary-compatible interface.
The interface provides an abstract service definition between the client and the object. This is in contrast to the object-oriented view of the world that places the object implementing the interface at the center. An interface is a logical grouping of method definitions that acts as the contract between the client and the service provider. Each provider is free to provide its own interpretation of the interface and its own implementation. To use a component, the client only needs to know the interface definition and have a binary component that implements that interface. This extra level of indirection between the client and the object provides for interchangeability between different implementations of the same interface, without affecting client code.
In traditional object-oriented programming, when developers wanted to define a service abstraction, they used an abstract class. The abstract class served to define a set of signatures that multiple classes would implement after deriving from the abstract class. By having a common base class to different service providers, they all became polymorphic with that service abstraction, and the client could potentially switch between providers with minimum changes. In .NET, you can still use abstract classes, but .NET also defines interfaces. The reserved word, interface, defines a CLR reference type that cannot have any implementation, cannot be instantiated, and has only public members. Saying that an interface cannot have implementation means that all interface methods and properties are abstract. Saying it cannot be instantiated means that the interface is an abstract class. For example, this interface definition:
public interface IMyInterface
{
void Method1();
void Method2();
void Method3();
}
is almost equivalent to this class definition:
public abstract class MyInterface
{
abstract public void Method1();
abstract public void Method2();
abstract public void Method3();
}
There are a few important differences between a pure abstract class (MustInherit in Visual Basic .NET) and an interface:
- An abstract class can still have implementation. It can have member variables and it can have non-abstract methods or properties. An interface, on the other hand, cannot have implementation or member variables.
- A .NET class can derive from only one base class, even if that base class is abstract. A .NET interface can derive from as many interfaces as required.
- An abstract class can derive from any other class or interface(s). An interface can derive only from other interfaces.
- An abstract class can have non-public (protected or private) methods and properties, even if they are all abstract. In an interface, by definition, all members are public.
- An abstract class can have static methods and static members. An abstract class can define constants. An interface can have none of those.
- An abstract class can have constructors. An interface cannot.
These differences are deliberate, not to restrict interfaces, but to provide for a formal public contract between a service provider (the classes implementing the interface) and the service consumer (the class's client). By disallowing any kind of implementation detail in interfaces (such as method implementation, constants, static members, and constructors), .NET promotes loose coupling between the service providers and the client. Because there is nothing in the contract that even hints at implementation, then by definition, the implementation is well encapsulated behind the interface, and the service providers are free to change their implementation without affecting the client.
You can even say that the interface acts like a binary shield, isolating both parties from each other. Because interfaces cannot be instantiated, .NET forces clients to choose a particular implementation to instantiate. Having only public members in an interface complements the contract semantics nicely: you would not want a contract with hidden clauses or "fine print." Everything the contract implies should be public and well defined. The more explicit and well defined a contract is, the less likely it is that there will be conflicts down the road regarding what exactly the class providing the service is required to do. The class implementing the interface must implement all the interface members without exception, because it is committing to provide this exact service definition.
An interface can only extend other interfaces, not classes. By deriving a new interface from an existing interface, you define a new and specialized contract, and the class implementing an interface must implement all members of the base interface(s). A class can choose to implement multiple interfaces, just as a person can choose to commit to multiple contracts.
Treating interfaces as binary contracts and shielding clients from changes made to the service providers is exactly the idea behind COM interfaces and, logically, .NET interfaces have the same semantics as COM interfaces. If you are an experienced COM developer or COM architect, then working with interfaces is second nature to you, and you will feel right at home with .NET interfaces.
Defining and Using Interfaces
To implement an interface, all a class has to do is derive from that interface. Listing 1 shows the class MyClass implementing the interface IMyInterface.
By disallowing any kind of implementation detail in interfaces (such as method implementation, constants, static members, and constructors), .NET promotes loose coupling between the service providers. The client interface acts like a binary shield, isolating both parties from each other.
As trivial as Listing 1 is, it does demonstrate a number of important points. First, interfaces have visibility?an interface can be private to its assembly (using the internal visibility modifier) or it can be used from outside the assembly (the public visibility modifier), as in the listing. Second, even though the methods the interface defines have no visibility modifiers, they are by definition public and the implementing class has to declare its interface methods as public. Third, there is no need for using new or override to qualify the method redefinition in the subclass because an interface method by nature cannot have any implementation and, therefore, there is nothing to override. Fourth, the class must implement all the methods the interface defines, without exception. If the class is an abstract class, then it can redefine the methods without providing concrete implementation.
To interact with the object using the interface, all a client has to do is down cast the object to the interface, similar to using any other base type. Using the same definitions as in Listing 1, the client code might be:
IMyInterface obj;
obj = new MyClass();
obj.Method1();
Interfaces promote loose coupling between clients and objects. When using interfaces, there is a level of indirection between the client code and the object implementing the interface. If the client was not responsible for instantiating the object, then there is nothing in the client code pertaining to the object hidden behind the interface shield. Because of this, there could be many possible implementations of the same interface, such as:
public interface IMyInterface
{...}
public class MyClass : IMyInterface
{...}
public class MyOtherClass : IMyInterface
{...}
When a client obtains an interface reference by creating an object of type MyClass, the client is actually saying to .NET, "give me the interpretation of MyClass to the way IMyInterface should be implemented."
Interfaces and Error Handling
This sort of casting down from a class instance to an interface:
IMyInterface obj;
obj = new MyClass();
obj.Method1();
is called implicit cast because the compiler is required to figure out which type to down cast the class to. When using implicit cast, the compiler enforces type safety. If the class MyClass does not implement the IMyInterface interface, then the compiler refuses to generate the code and produces a compilation error. The compiler is able to do that because it can read the class's metadata and can tell in advance that the class does not derive from the interface. However, there are a number of cases where developers use an explicit cast instead of the implicit cast. The first is for readability. Sometimes you want to convey to readers explicitly what interface to expect to get back:
obj.Method1();
IMyInterface obj;
/* Some code here */
obj = (IMyInterface) new MyClass();
obj.Method1();
Explicit down cast to the interface is at the expense of type safety. Even if the class does not support the interface, the compiler still compiles the client's code and .NET throws an exception at runtime. The second case where explicit cast is used is with class factories. In object-oriented programming, clients often do not create objects directly, but rather get their instances from some class factory (see the Abstract Factory design pattern in Object Oriented Design Patterns, pp 87, ADW 1994). In that case, explicit down cast is unavoidable because the class factory returns some generic base type, which is usually object:
IClassFactory factory;
/* Some code to initialize the class factory */
IMyInterface obj;
//GetObject() returns System.Object
obj = (IMyInterface)factory.GetObject();
obj.Method1();
The third case that mandates explicit down cast is when you have one interface the class implements, and you want to use it to get hold of another interface the class supports. Consider the code in Listing 2. Even when the client uses implicit cast to get hold of the first interface, it must do an explicit down cast to obtain the second.
In all of these examples of explicit down cast, you must incorporate error handling in case the type you are trying to down cast from does not support the interface. You can use try and catch statements to handle the exception, but you can also use the as operator to do a safe down cast. The as operator performs the cast if it is legal and assigns a value to the variable. However, if a down cast is not possible, the as operator assigns null to the interface variable, instead of throwing an exception. Listing 3 shows how to use the as operator to perform a safe cast that does not result in an exception in case of an error.
Interestingly, using the as operator to determine whether a particular object supports a given interface is semantically identical to COM's QueryInterface() method. Both mechanisms allow clients to defensively obtain an interface from an object and handle the situation when it does not support it.
Separation of Interface from Implementation
Separation of interface from implementation is a core principle of component-oriented programming. When separating interface from implementation, the clients program against an abstraction of the service (the interface) not a particular implementation of it (the object). As a result, changing an implementation detail on the server (or even switching to a different service provider altogether) does not affect the clients. (The client responsible for creating the object may still end up with one line of code pertaining to the object type, but even that one line can be hidden away using class factories.) However, unlike COM, .NET does not enforce separating interface from implementation. For example, using the definitions of Listing 1, the client's code could also be:
MyClass obj;
obj = new MyClass();
obj.Method1();
Meaning, because of the way the server in Listing 1 implements the interface (as public members), nothing prevents the client from programming directly against the object providing the service instead of the interface. I believe this is because .NET tries to make component-oriented programming accessible to every kind of developer, including those who have trouble with the more abstract concepts of interface-based programming. The fact that something is possible does not, of course, give permission to do it. Disciplined .NET developers should always enforce the separation to retain the benefits of interface-based programming. The following simple techniques will enable both server and client developers to provide the separation.
Explicit Interface Implementation
The server implementing the interface can actually prevent accessing the interface methods directly, using explicit interface implementation. Implementing an interface explicitly means qualifying the interface members' implementation with the interface that defines them:
public interface IMyInterface
{
void Method1();
void Method2();
}
public class MyClass : IMyInterface
{
void IMyInterface.Method1(){...}
void IMyInterface.Method2(){...}
//Other methods and members
}
Note that when using explicit implementation, the interface members must be defined at the class's scope as private (you cannot use any explicit visibility modifier, including private, on them). The only way clients can invoke the methods of explicitly implemented interfaces is by accessing them via the interface:
IMyInterface obj;
obj = new MyClass();
obj.Method1();
Using explicit interface implementation, server-side developers can rigorously enforce the separation of interface from implementation.
Defensive Client-Side Programming
Never assume an object supports an interface. In most cases, you should always program defensively on the client side, using the as operator as shown in Listing 3. Doing so caters both for robust error handling and for separation of interface from implementation, regardless of whether the server uses explicit interface implementation or not. Make it a habit on the client side to use the server via an interface and thus enforce the separation manually.
Assemblies with Interfaces Only
Because interfaces can be implemented by multiple parties, it is a good practice to put them in a separate assembly from that of the servers. This separate assembly contains interfaces only and is shared by both the server and the client developers. It is even possible to use class factories and have the server assemblies define only internal classes, which forces the clients to use the public interfaces. A separate interface assembly also allows for concurrent development of the server and the client (once the two parties have agreed on the interfaces). Assemblies with interfaces only extend the separation of interface from implementation further down to the code-packaging units.
Interface Methods, Properties and Events
An interface is not limited to defining methods only. An interface can define methods, properties, indexers and events. Listing 4 shows the syntax for defining all of those in an interface, and the corresponding implementation.
Interfaces and Structs
An interesting use of interfaces with properties involves structs. In .NET, a struct cannot have a base struct or a base class because it is a value type. However, .NET does permit structs to derive from one or more interfaces. The reason for this is that sometimes you want to define abstract data storage and there are a number of possible implementations for the actual structure. By defining an interface (preferably with properties only, but it can have methods as well), you could pass around that interface, instead of the actual struct, and gain the benefits of polymorphism, even though structs are not allowed to derive from a common base struct. Listing 5 demonstrates the use of an interface (with properties only) as a base type for structs.
More on Implementing Interfaces
A class could derive from as many interfaces as required (see Listing 2), but a class is limited to only one base class at the most. When deriving from a base class and from interfaces, the base must be listed first in the derivation chain, and this is enforced by the compiler:
public interface IMyInterface
{}
public interface IMyOtherInterface
{}
public class MyBaseClass
{}
public class MySubClass : MyBaseClass,
IMyInterface,IMyOtherInterface
{}
Even such a trivial example raises a number of questions: what if both interfaces define identical methods? What are the ways to resolve such collusions? What if the base class already derives from one or more of the interfaces? Let's address these questions.
Interface Methods Collusion
When deriving a class from two or more interfaces that define an identical method, you have two options: the first is to channel both interface methods to the same actual method implementation. The second is to provide separate method implementations. For example, consider two interfaces that define the identical method Method1():
public interface IMyInterface
{
void Method1();
}
public interface IMyOtherInterface
{
void Method1();
}
If you want to channel both interface methods to the same method implementation, then all you have to do is derive from the interfaces and implement the method once:
public class MyClass : IMyInterface,IMyOtherInterface
{
public void Method1(){...}
//Other methods and members
}
Regardless of which interface the client of MyClass chooses to use, calls to Method1() are channeled to that single implementation:
IMyInterface obj1;
IMyOtherInterface obj2;
obj1 = new MyClass();
obj1.Method1();
obj2 = (IMyOtherInterface)obj1;
obj2.Method1();
To provide separate implementations, you need to use explicit interface implementation by qualifying the method implementation with the interface that defines it:
public class MyClass : IMyInterface,IMyOtherInterface
{
void IMyInterface.Method1(){...}
void IMyOtherInterface.Method1(){...}
//Other methods and members
}
Now, when the client calls an interface method, that interface-specific method is called.
Interfaces and Class Hierarchy
In component-oriented programming, you focus on defining and implementing interfaces. In object-oriented programming, you model your solution using class hierarchies. The question is, how do the two concepts interact? The answer depends on the way you override or redefine the interface methods at the different levels of the class hierarchy. Consider the code in Listing 6:
In a typical class-hierarchy, the top-most base class should derive from the interface, providing polymorphism to all sub classes with the interface. The top-most base class must also define all the interface members as virtual so that sub classes could override them. Each level of the class hierarchy would then override its preceding level (using the override inheritance qualifier), as shown in Listing 6. When the client uses the interface, it will get the desired interpretation of the interface. For example, if the client code is:
ITrace obj = new B();
obj.TraceSelf();
Then the object would trace "B" to the output Window as expected. Things get less obvious if the subclasses use the new inheritance qualifier. The new modifier gives only subclass behavior when dealing with an explicit reference to a subclass, such as:
B obj = new B();
In all other cases, the base class implementation is used. If the code in Listing 6 was:
public class A : ITrace
{
public virtual void TraceSelf()
{
Trace.WriteLine("A");
}
}
public class B : A
{
public new void TraceSelf()
{
Trace.WriteLine("B");
}
}
public class C : B
{
public new void TraceSelf()
{
Trace.WriteLine("C");
}
}
Then the client code:
ITrace obj = new B();
obj.TraceSelf();
would now trace "A" to the output Window instead of "B." Note that this is exactly why the new inheritance visibility modifier is available in the first place. Imagine a client that somehow depends on the base class particular implementation. If a new subclass is used instead of the base class, the new modifier ensures that the client will get the implementation it expects. However, this nuance makes sense only when dealing with clients that are not using interface-based programming but rather program directly against the objects:
A obj = new B();
obj.TraceSelf();//Traces "A"
You can still support such clients, however, and provide interface-based services to the rest of the clients. To achieve that, each of the classes in the hierarchy can reiterate its polymorphism with the interface by explicitly deriving from it (on top of having the base class derive from the interface). Doing so (as shown in Listing 7), makes the new modifier yield the same result as the override modifier for the interface-based clients:
ITrace obj = new B();
obj.TraceSelf();//Traces "B"
In general, I prefer code such as Listing 6 using the override visibility modifier with virtual interface members at the top-most base class. Such code is readable and straightforward. Code such as Listing 7 makes for an interesting exercise, but rarely is of practical use.
Interface Factoring and Design
Syntax aside, how would you go about designing interfaces? How would you know which methods to allocate to which interface? How many members should each interface have? Answering these questions has little to do with .NET and a lot to do with abstract component-oriented analysis and design. An in-depth discussion of decomposing a system into components and discovering interface methods and properties is beyond the scope of this article. Nonetheless, here is some advice to guide you in your interface design effort.
An interface is a grouping of logically related methods and properties. What constitutes "logically related" is usually domain specific. You can think of interfaces as different facets of the same entity. Once you have identified (after requirements analysis) all the operations and properties the entity supports, you need to allocate them to interfaces. This is called interface factoring. When factoring an interface, always think in terms of reusable elements. Given that the interface is the basic unit of reuse in a component-oriented application, would this particular interface factoring yield interfaces that could be reused by other entities in the system? What facets of the entity could be logically factored out and used by other entities? An example would go a long way to demystify interface factoring. Suppose you are trying to model a dog. The requirements are that a dog be able to bark and fetch and have a veterinarian clinic registration number as well as a property for having received shots. You could define the IDog interface and have different kinds of dogs, such as poodle and German shepherd implement the IDog interface:
public interface IDog
{
void Fetch();
void Bark();
ulong VetClinicNumber{ get; set; }
bool HasShots{ get; set; }
}
public class Poodle : IDog
{...}
public class GermanShepherd : IDog
{...}
However, such a composition of the IDog interface is not well factored. The reason is that even though all of the interface members are things a dog should support, Fetch and Bark are more logically related to each other than to VetClinicNumber and HasShots. Fetch() and Bark() are one facet of the dog as a living active entity, while VetClinicNumber and HasShots are a different facet of the dog?a facet that relates to it as a record of a pet in a veterinarian clinic. A better approach is to factor out the VetClinicNumber and HasShots properties into a separate interface called IPet:
public interface IPet
{
ulong VetClinicNumber{ get; set; }
bool HasShots{ get; set; }
}
public interface IDog
{
void Fetch();
void Bark();
}
Because the pet facet is independent of the canine facet, you could have other entities (such as cats) reuse the IPet interface and support it:
public interface IPet
{
ulong VetClinicNumber{ get; set; }
bool HasShots{ get; set; }
}
public interface IDog
{
void Fetch();
void Bark();
}
public interface ICat
{
void Purr();
void CatchMouse();
}
public class Poodle : IDog,IPet
{...}
public class Siamese : ICat, IPet
{...}
This enables you to decouple the clinic management aspect of the application from the actual pet types (be it dogs or cats). Factoring out operations and properties into separate interfaces is usually done when there is a weak logical relation between methods. However, sometimes, identical operations are found in several unrelated interfaces, and these operations are logically related to their respective interfaces. For example, both cats and dogs need to shed fur and lactate their offspring. Logically, shedding is just as much a dog operation as barking, and shedding is just as much a cat operation as purring. In such cases, you can factor interfaces into hierarchy of interfaces instead of separate interfaces:
public interface IMammal
{
void ShedFur();
void Lactate();
}
public interface IDog : IMammal
{
void Fetch();
void Bark();
}
public interface ICat : IMammal
{
void Purr();
void CatchMouse();
}
Interface Factoring Metrics
As you can see, proper interface factoring results in more specialized, loosely coupled, fine-tuned and reusable interfaces and, subsequently, systems. In general, interface factoring results in interfaces with fewer members. However, when designing a system, you need to balance out two counter forces: if you have too many granular interfaces, the overall cost of interacting with all these interfaces will be prohibiting. On the other hand, if you have only a few complex, poorly factored interfaces, the cost of interacting with these interfaces will be prohibiting. Because these interface-factoring issues are independent of the component technology used, I can draw from my own and others' experiences of factoring and architecting large-scale COM applications and share a few rules of thumb and metrics I have collected about interface factoring.
An interface with just one member is possible, but is it something to avoid. Because an interface is a facet of an entity, that facet must be pretty dull if you can express it with just one method or property. Examine that single method: is it using too many parameters? Is it too coarse and would it be more useful if factored into several methods? Should you factor this method or property into an already existing interface?
The optimal number of interface members (in my opinion and experience) is a range, between three and five, which means that this is the range you should aim toward. If you design an interface with more members, say six to nine members, you are still doing relatively well, but try and look at the members to see which of them can be consolidated, since it is quite possible to over-factor methods and properties. If you have an interface with 12 or more methods, you should definitely find ways of factoring the members into either separate interfaces or a hierarchy of interfaces. Your coding standards should set some upper limit of interface members that should never be exceeded regardless of the circumstances. A possible upper number is 20.
Another rule of thumb is to design in a useful ratio of methods, properties and events as interface members. Interfaces allow clients to invoke abstract operations, without caring about actual implementation details. Properties are known as just-enough-encapsulation. It is much better to expose a member variable as a property than to give direct access to it because then you can encapsulate the business logic of setting and reading that variable value in the object, instead of spreading it all over the clients. A better approach would be not to bother clients with properties at all. Clients should invoke methods and let the object worry about how to manage its state.
As a result, interfaces should have more methods than properties, as a ratio of at least 2:1. The one exception is interfaces that do nothing except define properties. Such interfaces should have properties only and no methods. I don't think that defining events as interface members is a good idea, and you should avoid it as much as possible. Leave it up to the object to decide if it needs an event member variable or not.