Base Class Usage Guidelines

A class is the most common kind of type. A class can be abstract or sealed. An abstract class requires a derived class to provide an implementation. A sealed class does not allow a derived class. It is recommended that you use classes over other types.

Base classes are a useful way to group objects that share a common set of functionality. Base classes can provide a default set of functionality, while allowing customization though extension.

You should explicitly provide a constructor for a class. Compilers commonly add a public default constructor to classes that do not define a constructor. This can be misleading to a user of the class, if your intention is for the class not to be creatable. Therefore, it is best practice to always define at least one constructor for a class. If you do not want it to be creatable, make the constructor private.

You should add extensibility or polymorphism to your design only if you have a clear customer scenario for it. For example, providing an interface for data adapters is difficult and serves no real benefit. Developers will still have to program against each adapter specifically, so there is only marginal benefit from providing an interface. However, you do need to support consistency between all adapters. Although an interface or abstract class is not appropriate in this situation, providing a consistent pattern is very important. You can provide consistent patterns for developers in base classes. Follow these guidelines for creating base classes.

Base Classes vs. Interfaces

An interface type is a specification of a protocol, potentially supported by many object types. Use base classes instead of interfaces whenever possible. From a versioning perspective, classes are more flexible than interfaces. With a class, you can ship Version 1.0 and then in Version 2.0 add a new method to the class. As long as the method is not abstract, any existing derived classes continue to function unchanged.

Because interfaces do not support implementation inheritance, the pattern that applies to classes does not apply to interfaces. Adding a method to an interface is equivalent to adding an abstract method to a base class; any class that implements the interface will break because the class does not implement the new method.

Interfaces are appropriate in the following situations:

  • Several unrelated classes want to support the protocol.
  • These classes already have established base classes (for example, some are user interface (UI) controls, and some are XML Web services).
  • Aggregation is not appropriate or practical.

In all other situations, class inheritance is a better model.

Protected Methods and Constructors

Provide class customization through protected methods. The public interface of a base class should provide a rich set of functionality for the consumer of the class. However, users of the class often want to implement the fewest number of methods possible to provide that rich set of functionality to the consumer. To meet this goal, provide a set of nonvirtual or final public methods that call through to a single protected method that provides implementations for the methods. This method should be marked with the Impl suffix. Using this pattern is also referred to as providing a Template method. The following code example demonstrates this process.

Public Class SampleClass
   
   Private x As Integer
   Private y As Integer
   Private width As Integer
   Private height As Integer
   Private specified As BoundsSpecified
   
   Overloads Public Sub SetBounds(x As Integer, y As Integer, width As 
         Integer, height As Integer)
      SetBoundsImpl(x, y, width, height, Me.specified)
   End Sub 
   
   Overloads Public Sub SetBounds(x As Integer, y As Integer, width As 
         Integer, height As Integer, specified As BoundsSpecified)
      
      SetBoundsImpl(x, y, width, height, specified)
   End Sub 

   Protected Overridable Sub SetBoundsImpl(x As Integer, y As Integer,  
         width As Integer, height As Integer, 
         specified As BoundsSpecified)
      ' Insert code to perform meaningful operations here.
      Me.x = x
      Me.y = y
      Me.width = width
      Me.height = height
      Me.specified = specified      
      Console.WriteLine("x {0}, y {1}, width {2}, height {3}, bounds {4}", 
         Me.x, Me.y, Me.width, Me.height, Me.specified)
   End Sub
End Class 
[C#]
public class MyClass
{
  private int x;
  private int y;
  private int width;
  private int height;
  BoundsSpecified specified;

  public void SetBounds(int x, int y, int width, int height)
   {
      SetBoundsImpl(x, y, width, height, this.specified);
   }

   public void SetBounds(int x, int y, int width, int height,
      BoundsSpecified specified)
   {    
      SetBoundsImpl(x, y, width, height, specified);
   }

   protected virtual void SetBoundsImpl(int x, int y, int width, int 
      height, BoundsSpecified specified)
   {
         // Add code to perform meaningful opertions here.
         this.x = x;
         this.y = y;
         this.width = width;
         this.height = height;
         this.specified = specified;
   }
}

Many compilers, such as the C# compiler, insert a public or protected constructor if you do not. Therefore, for better documentation and readability of your source code, you should explicitly define a protected constructor on all abstract classes.

Sealed Class Usage Guidelines

Use sealed classes if there are only static methods and properties on a class.

See Also

Design Guidelines for Class Library Developers | Type Usage Guidelines | Class Naming Guidelines