Understanding Interface-Based Programming

In this final part of a four-part series focused on interface-based programming, you'll learn about interface factoring, factoring metrics, and more. This article is excerpted from chapter three of Programming .NET Components, Second Edition, written by Juval Lowy (O'Reilly, 2006; ISBN: 0596007620). Copyright © 2006 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 3
June 14, 2007
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

Generic Interface Methods

In C# 2.0, an interface method can define generic type parameters, specific to its execution scope:

  public interface IMyInterface<T>
  {
    void MyMethod<X>(T t,X x);
  }

This is an important capability, because it allows you to call the method with a different type every time, which is often very handy for utility classes.

You can define method-specific generic type parameters even if the containing interface does not use generics at all:

  public interface IMyInterface
  {
    void MyMethod<T>(T t);
  }

This ability is for methods only. Properties or indexers can use only generic type parameters defined at the scope of the interface.

When you call an interface method that defines generic type parameters, you can provide the type to use at the call site:

  public class MyClass : IMyInterface
  {
    public void MyMethod<T>(T t)
    {...}
 
}
  IMyInterface obj = new MyClass();
  obj.MyMethod<int>(3);

That said, when the method is invoked, the C# compiler is smart enough to infer the correct type based on the type of parameter passed in, and it allows you to omit the type specification altogether:

  IMyInterface obj = new MyClass();
  obj.MyMethod(3);

This ability is called generic type inference. Note that the compiler cannot infer the type based on the type of the returned value alone:

  public interface IMyInterface
  {
   
T MyMethod<T>();
  }
  public class MyClass : IMyInterface
  {
   
public T MyMethod<T>()
   
{...}
  }
  IMyInterface obj = new MyClass();
  int number = obj.MyMethod();//Does not compile

When an interface method defines its own generic type parameters, it can also define constraints for these types:

  public interface IMyInterface
  {
   
void MyMethod<T>(T t) where T : IComparable<T>;
  }

However, my recommendation to avoid constraints at the interface level extends to method-level generic type parameters as well.

Designing and Factoring Interfaces

Syntax aside, how do you go about designing interfaces? How do 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 how to decompose a system into components and how to discover interface methods and properties is beyond the scope of this book. Nonetheless, this section offers a few pieces of advice to guide you in your interface-design effort.

Interface Factoring

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 a requirements analysis) all the operations and properties the entity supports, you need to allocate them to interfaces. This is called interface factoring. When you factor an interface, always think in terms of reusable elements. In a component-oriented application, the basic unit of reuse is the interface. Would this particular interface factoring yield interfaces that other entities in the system can reuse? What facets of the entity can logically be factored out and used by other entities?

Suppose you wish to model a dog. The requirements are that the dog be able to bark and fetch and that it have a veterinary clinic registration number and a property for having received shots. You can define theIDoginterface and have different kinds of dogs, such asPoodle andGermanShepherd, implement theIDog interface:

  public interface IDog
 
{
    void  Fetch();
    void  Bark();
    long  VetClinicNumber{get;set;}
    bool  HasShots{get;set;}
 
}
  public class Poodle : IDog
  {...}

  public class GermanShepherd : IDog
  {...}

However, such a composition of theIDoginterface isn’t well-factored. Even though all the interface members are things a dog should support,FetchandBarkare more logically related to each other than toVetClinicNumberandHasShots.Fetch()andBark() involve one facet of the dog, as a living, active entity, whileVetClinicNumberandHasShotsinvolve a different facet, one that relates it as a record of a pet in a veterinary clinic. A better approach is to factor out theVetClinicNumberandHasShotsproperties to a separate interface calledIPet:

  public interface IPet
 
{
    long  VetClinicNumber{ get; set; }
    bool  HasShots{ get; set; }
 
}
  public interface IDog
  {
   
void Fetch();
    void Bark();
  }

Because the pet facet is independent of the canine facet, other entities (such as cats) can reuse theIPetinterface and support it:

  public interface IPet
  {
    long  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 factoring, in turn, allows you to decouple the clinic-management aspect of the application from the actual pet type (be it dogs or cats). Factoring operations and properties into separate interfaces is usually done when there is a weak logical relation between methods. However, identical operations are sometimes 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 feed their offspring. Logically, shedding is just a dog operation, as is barking, and is also just a cat operation, as is purring. In such cases, you can factor the interfaces into a 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();
  }

Factoring Metrics

As you can see, proper interface-factoring results in more specialized, loosely coupled, fine-tuned, and reusable interfaces, and subsequently, those benefits apply to the system as well. In general, interface factoring results in interfaces with fewer members. When you design a component-based system, however, you need to balance out two countering forces (see Figure 3-1).

If you have too many granular interfaces, it will be easy to implement each interface, but the overall cost of interacting with all those interfaces will be prohibitive. On the other hand, if you have only a few complex, large, poorly factored interfaces, the cost of implementing those interfaces will be a prohibitive factor, even though the cost of interacting with them might be low. In any given system, the total effort involved in designing and maintaining the components that implement the interfaces is the sum of those two factors. As you can see from Figure 3-1, there is an area of minimum


Figure 3-1.   Balancing the number of components and their size

cost or effort in relation to the size and number of interfaces. Because these interface-factoring issues are independent of the component technology used, I can extrapolate from my own and others’ experiences of factoring and architecting large-scale applications and share a few rules of thumb and metrics I have collected about interface factoring.

Interfaces with just one member are possible, but you should avoid them. An interface is a facet of an entity, and 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 therefore, should it be 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 between three and five. If you design an interface with more members—say, six to nine—you are still doing relatively well. However, try to look at the members to determine whether any can be collapsed into each other, since it’s quite possible to over-factor methods and properties. If you have an interface with 12 or more methods, you should definitely find ways to factor the members into either separate interfaces or a hierarchy of interfaces. Your coding standard should set some upper limit never to be exceeded, regardless of the circumstances (say, 20).

Another rule involves the ratio of methods to properties among interface members. Interfaces allow clients to invoke abstract operations, without caring about actual implementation details. Properties are what is known as just-enough-encapsulation. It’s 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’s value in the object, instead of spreading it over the clients. Ideally, you shouldn’t bother clients with properties at all. Clients should invoke methods and let the object worry about how to manage its state. Consequently, interfaces should have more methods than properties, by a ratio of at least 2:1. The one exception is interfaces that do nothing except define properties; such interfaces should have properties only, with no methods.

It’s best to avoid defining events as interface members, if possible. Leave it up to the object to decide whether it needs an event member variable or not. As you’ll see in Chapter 6, there is more than one way to manage events.


Is .NET Well-Factored?

After writing down my rules of thumb and metrics for interface factoring, I was curious to see how the various interfaces defined by the .NET Framework look in light of these points. I examined more than 300 interfaces defined by .NET. I excluded from the survey the COM interoperation interfaces redefined in .NET, because I wanted to look at native .NET interfaces only. I also excluded from the results the outliers—interfaces with 0 members and interfaces with more than 20 members. I consider an interface with more than 20 members to be a poorly factored one, not to be used as an example. On average, a .NET Framework interface has 3.75 members, with a methods-to-properties ratio of 3.5:1. Less than 3% of the members are events. These metrics nicely reaffirm the rules of thumb outlined in this section; you could say that on average, .NET interfaces are well-factored.

 


A word of caution about factoring metrics: rules of thumb and generic metrics are only tools to help you gauge and evaluate your particular design. There is no substitute for domain expertise and experience. Always be practical, apply judgment, and question what you do in light of the metrics.

Interfaces in Visual Studio 2005

Visual Studio 2005 has excellent support for implementing and refactoring interfaces. As a component developer, your classes will occasionally need to implement an interface defined by another party. Instead of copying and pasting the interface definition, or typing it in, you can use Visual Studio 2005 to generate a skeletal implementation of the interface. A skeletal implementation is a do-nothing implementation: all implanted methods or properties throw an exception of type Exception and do not contain any other code. A skeletal implementation is required to at least get the code compiled as a starting point for your implementation of an interface, and it prevents clients from consuming a half-baked implementation. To have Visual Studio 2005 generate a skeletal interface implementation, you first add the interface to the class derivation chain. When you finish typing the interface name (such as IMyInterface), Visual Studio 2005 marks a little underscore tag under the I of the interface name. If you hover over IMyInterface, Visual Studio 2005 pops up a smart tag with a tool tip, “Options to implement interface.” If you click the down arrow of the smart tip you can select from two options in the menu, implementing the interface either implicitly or explicitly (see Figure 3-2).


Figure 3-2.  Using Visual Studio 2005 to provide a skeletal interface implementation

Once you select an option, Visual Studio 2005 creates a skeletal implementation of the interface on your class and scopes it with a collapsible#regiondirective. For example, consider this interface definition:

  public interface IMyInterface
 
{
    void Method1();
    int Method2(int number);
    string Method3();
 
}

If you select explicit implementation, Visual Studio 2005 generates this skeletal implementation:

  public class MyClass : IMyInterface
  {
    #region IMyInterface Members
    void IMyInterface.Method1()
    {
     
throw new Exception();
    }
    int IMyInterface.Method2(int number)
    {
     
throw new Exception();
    }
    string IMyInterface.Method3()
    {
     
throw new Exception();
    }
    #endregion
 
}

Implementing a skeletal interface also works with generic interfaces.

Visual Basic 2005 has only IntelliSense-level skeletal interface implementation, and it doesn’t generate a region around the skeleton.

Interface Refactoring

Code refactoring allows you to change the code structure without changing or affecting what the code itself actually does. Changing a variable name or packaging a few lines of code into a method are examples of code refactoring. Visual Studio 2005 contains a simple refactoring engine that enables several actions, including renaming types, variables, methods, or parameters; extracting a method out of a code section (and inserting a method call instead); encapsulating type members as properties; automating many formatting tasks; and auto-expanding common statements. The main difference between C# refactoring and doing a mere edit or find-and-replace is that you can harness the intelligence of the compiler to distinguish between code and comments, and so on.

In Visual Studio 2005, refactoring changes are limited to a single solution and do not propagate to client assemblies in different solutions.

You can invoke refactoring in two ways: you can select Refactor from the top-level Visual Studio 2005 menu, or you can select it from the pop-up context menu.

Of particular interest in the context of this chapter is the refactoring ability to extract an interface definition out of a set of public methods the type implements. For example, consider the followingCalculatorclass:

  public abstract class Calculator
  {
   
public int Add(int argument1,int argument2)
    
{
     
return argument1 + argument2;
    }
    public int Subtract(int argument1,int argument2)
    {
     
return argument1 - argument2;
    }
    public virtual int Divide(int argument1,int argument2)
    {
     
return argument1 + argument2;
   
}
   
public abstract int Multiply(int argument1,int argument2);
  }

To extract an interface out of theCalculatorclass, right-click anywhere inside the class definition and select Extract Interface… from the Refactor menu. This will bring up the Extract Interface dialog box, shown in Figure 3-3.

The dialog box will propose a name for the interface: the type’s name, prefixed with anI. The refactoring will use the default (also called root) namespace of the project, as configured in the project settings.

  


Figure 3-3.  The Extract Interface dialog box

The interface will be extracted to a separate file, which will automatically be added to the project. You can provide a filename in the dialog. Finally, all the public methods (or properties) of the type will be listed in the dialog, regardless of whether they are public, virtual, or abstract. Note that when a class hierarchy is involved, the refactoring engine will only include public methods explicitly declared by the class or overridden by it. To include the suggested methods in the new interface definition, you must explicitly check the checkboxes to the left of each method. After you click the OK button, the new interface will be placed in the specified new file, and Visual Studio 2005 will add the interface derivation to theCalculator class, as shown here:

  //In the file ICalculator.cs
  interface ICalculator
  {
   
int Add(int argument1,int argument2);
   
int Divide(int argument1,int argument2);
   
int Multiply(int argument1,int argument2);
   
int Subtruct(int argument1,int argument2);
  }

  //In the file Calculator.cs
  public abstract class Calculator : ICalculator
  {...}

Note that the extracted interface is internal, and that you have to explicitly make it public.

You can also extract one interface from the definition of another, in which case the new interface will be placed in a new file, but the original interface definition will not change (i.e., it will not inherit from the new interface). Visual Studio 2005 will not prefix the new interface name with anI, because that would result in a doubleII. Instead, it will append an ordinal number to the interface name.

Note that if the type already implements an interface implicitly, that interface’s members will be included in the list in the Extract Interface dialog. Use explicit interface implementation on existing interfaces to avoid including them in the refactoring.

 


* See the Abstract Factory design pattern in Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley).

 

* If you are not familiar with generics, Appendix D provides a concise overview.

 

blog comments powered by Disqus
.NET ARTICLES

- .Net 4.5 Brings Changes
- Understanding Events in VB.NET
- Objects, Properties, Events and Methods in V...
- Install Visual Web Developer Express 2010
- Microsoft Gadgeteer an Open Source Alternati...
- Best DotNetNuke Modules
- Facebook Image Viewer in Visual Basic
- Murach`s ADO.NET 4 Database Programming with...
- 5 Must Have Visual Studio 2010 Extensions
- Dynamic Web Applications with ASP.NET Mono u...
- PDFSharp: HTML to PDF in ASP.NET 3.5 using V...
- Using the PDFSharp Library in ASP.NET 3.5 wi...
- Sending Email in ASP.NET 3.5 using VB.NET wi...
- ASP.NET 3.5 Role Based Security and User Aut...
- Creating ASP.NET Login Web Pages and Basic C...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 3 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials