Export (0) Print
Expand All
Expand Minimize

Inheritance and Interfaces

 

Andy Baron
MCW Technologies

February 2002

Summary: Learn about the differences between class inheritance and interface implementation in Microsoft Visual Basic .NET. (12 printed pages)

Objectives

  • Explore the concepts behind the use of inheritance and interfaces.
  • Learn when to use class inheritance and when to use interface implementation.

Assumptions

  • Familiarity with the Microsoft® Visual Basic® .NET language
  • Familiarity with Microsoft Visual Basic 6.0
  • You have a basic understanding of terms involved with inheritance.

Contents

Object-Oriented Programming: Why Bother?
Inheritance Hierarchies
Creating and Implementing Interfaces
Comparing Class Inheritance and Interface Implementation
Object Composition
What's Different From Visual Basic 6.0?
Summary

Object-Oriented Programming: Why Bother?

Only a small percentage of Visual Basic 6.0 programmers ever found the need to create any class modules other than the ones that were automatically created when they built Visual Basic 6.0 forms. In addition, most programmers who did create their own class modules in Visual Basic 6.0 did so only because they wanted to build Microsoft ActiveX® components and they had to use class modules to do it.

In Visual Basic .NET, as in all the other Microsoft .NET languages, object-oriented programming (OOP) is not an option—it's a requirement. Every piece of code you write is part of a type of some kind, either a class, an interface, a struct (structs are value types similar to the user-defined types in Visual Basic 6.0), or an enumeration. Even the seemingly stand-alone procedures in Visual Basic .NET modules are actually implemented behind the scenes as shared methods of hidden classes.

So, what's so great about OOP anyway? Why is Microsoft requiring you to learn how to use it to program in Visual Basic .NET?

Handling Complexity and Change

There are two pervasive software development problems that OOP evolved to solve: handling complexity and handling change. OOP makes it easier for us to design and work with complex software systems, and easier to modify those systems without breaking them.

As a Visual Basic 6.0 programmer, you've probably needed to work with the DAO and/or ADO object models to retrieve and manipulate data. Imagine how tough it would have been to do that work if, instead of using the methods and properties of objects like recordsets, you had been required to call a separate function for every operation you needed to perform. For example, to load a list box using either DAO or ADO, you write about ten lines of code. Using the ODBC API functions directly, you wrote about 50! As you can see, the OOP approach is much easier to implement.

As another example, consider the Microsoft Windows® API. If you spent much time doing Windows API calls or working with other procedure-based APIs, you know how easy it is to make a mistake and how hard it is to remember which function or sub to call for each task. So, modeling problems by creating groups of related objects is friendlier to programmers than making them work with a big list of subs and functions.

Writing and debugging code is hard, but most programmers enjoy tackling a problem, figuring out a clever way to solve it, and then fiddling with the code until everything meets the customer's requirements. That's the fun part, but it's rarely the end of the story. One task that programmers uniformly dread is having to dig into code that someone else wrote (or that they wrote in the dim, distant past), to have to figure out how it all fits together and how to make a change that meets some new requirement—all without causing unintended consequences. Unfortunately, in the life cycle of most systems, code maintenance ends up consuming much more programmer time than code creation.

Encapsulating functionality within objects supports change by creating a clear separation between the publicly exposed properties, methods, and events of a class and the hidden implementation of those class members. As long as the public interface is preserved, the private implementation can safely change. That's the goal anyway. We'll look at ways you can help ensure that this goal is realized.

Inheritance and Interfaces

In addition to making complex systems easier to create and modify by encapsulating functionality within the methods and properties of objects, OOP supports class inheritance and the implementation of interfaces as ways of allowing new classes to be safely added to existing systems without requiring changes to existing code. We'll compare these two approaches to preparing for change, look at when each one is appropriate, and discuss a third approach, object composition, which is often the best choice of all.

Inheritance Hierarchies

Of all the OOP techniques available in Visual Basic .NET, inheritance is the least familiar to programmers coming from Visual Basic 6.0, where inheritance wasn't available. Even the non-Visual Basic object models that Visual Basic 6.0 programmers were likely to use, such as ADO or the Office libraries, rarely required an understanding of inheritance.

Inheritance allows you to create a new class that is a variation on an existing (base) class, and to substitute the new derived class in any situation that originally called for the old base class. For example, if you have a class called SalesOrder, you can create a new derived class called WebSalesOrder:

Public Class WebSalesOrder
  Inherits SalesOrder

Any existing methods that take an object of type SalesOrder can remain unchanged and will now be able to handle WebSalesOrder objects as well. If SalesOrder has a Confirm method, you are assured that WebSalesOrder will also have a Confirm method. But if Confirm is marked as Overridable in the SalesOrder class, you can create a new Confirm method in the WebSalesOrder class that overrides the original method:

  Public Overrides Sub Confirm

Perhaps the original Confirm method generated a fax to the customer, and the one in WebSalesOrder uses e-mail instead. The important part is that a method originally set up to accept objects of type SalesOrder will continue to work if you pass it a WebSalesOrder. And if that method calls the Confirm method of the object, the customer will get an email rather than a fax. Old code calls new code without making any changes to the old code.

On the other hand, suppose there is a Total method that can work exactly the same for both classes. The derived WebSalesOrder class doesn't need to include any mention of that method—its implementation will automatically be inherited from the base class, and you'll be able to call the Total method for any WebSalesOrder object and have it act just like a SalesOrder object.

Polymorphism

Polymorphism, or substitutability, is one clear benefit of inheritance: any time you create an object from a derived class, you can use that object anywhere you can use an object from the base class. A WebSalesOrder "is a" SalesOrder, and it must be able to do everything a SalesOrder does, even if it does it in its own distinctive way.

Polymorphism is the key to many of the advantages to OOP, and, as you'll see later in this document, it is not only available when you use inheritance, but also when you use interfaces. Polymorphism allows different kinds of objects that are all able to process a common set of messages to be used interchangeably, and to process those messages in their own ways.

Your code to confirm orders doesn't have to know how to perform the confirmation, and it doesn't even have to know what kind of order is being confirmed. It only cares that it can successfully call the Confirm method of the order object it is working with, and it can rely on the object to handle the confirmation details:

Public Sub ProcessOrder(order As SalesOrder)
  order.Confirm

When you call the ProcessOrder procedure, you may pass it either a SalesOrder object or a WebSalesOrder object, and they will both work.

Virtual Methods and Properties

Virtuality only comes into play when you override base class methods in derived classes, and it is perhaps the most magical part of inheritance. The magic is that the Microsoft .NET runtime automatically finds and runs the most specialized implementation of any method or property that is called.

For example, calling order.Confirm in the example above reaches down to the Confirm method of WebSalesOrder, where the base SalesOrder.Confirm method was overridden. However, order.Total calls the Total method of SalesOrder, because no specialized Total method was created in WebSalesOrder.

Abstract Classes and Methods

An abstract class is one that includes at least one method or property that must be overridden. For example, you could have created a SalesOrder class that didn't implement any way of confirming an order. Knowing that different kinds of orders must be confirmed in different ways, you can specify that a class derived from SalesOrder must override the Confirm method and provide its own implementation.

This means that you couldn't ever create SalesOrder objects. Instead, you would only be able to create objects using derived classes that filled in the necessary details of how to perform an order confirmation. For that reason, the SalesOrder class would be marked MustInherit:

Public MustInherit Class SalesOrder
  Public MustOverride Sub Confirm()
  Public Sub Total()
    'Code to calculate a total
  End Sub

If all the members of a class must be overridden, the class is called a pure abstract class. For example, your SalesOrder class could be just a list of all the methods and properties that need to be overridden by any derived class. But even if only one member must be overridden, the class will be identified as abstract using the MustInherit attribute.

Type-Checking and Downcasting

Now, what if some kinds of orders need confirmation and some don't? You could use a base SalesOrder class that doesn't include a Confirm method and only add Confirm to those derived classes that need it. But this will create a problem: You may want to create a procedure to handle all kinds of orders and confirm those that need confirmation. How would you know which ones to confirm?

You could give the procedure a parameter of type SalesOrder, allowing all types derived from SalesOrder to be passed in. But then you would need to test for all the different confirmable types before calling the Confirm method. Furthermore, once you found an order type that was confirmable, you would need to downcast from the SalesOrder type, which does not have a Confirm method, to the derived type that does:

Public Sub ProcessOrder(order As SalesOrder)
 If TypeOf(order) Is WebSalesOrder Then
   CType(order, WebSalesOrder).Confirm
 ElseIf TypeOf(order) Is EmailSalesOrder Then
   CType(order, EmailSalesOrder).Confirm
 ' and so on

This kind of code is very hard to maintain. It forces you to modify the method any time a new type derived from SalesOrder is added to the system. That kind of coupling between old code and new code is exactly what you want to avoid.

This would be almost as bad as implementing the confirmation code in this procedure for each different kind of sales order:

If TypeOf(order) Is WebSalesOrder Then
   ' Put code here to confirm a WebSalesOrder
ElseIf TypeOf(order) Is EmailSalesOrder Then
   ' Put code here to confirm an EmailSalesOrder
' and so on

That pattern is even worse; not only must you change the procedure for every new type you create, you also no longer have each sales object handling its own confirmation, making it much harder for a programmer who needs to add a new type of sales order. How would that programmer know all the places where new code is needed to handle the new type?

Another approach would be to create an intermediate abstract type called ConfirmableSalesOrder, derived from SalesOrder, with a MustOverride method, Confirm. You derive any types that need to handle confirmation from ConfirmableSalesOrder, and derive any other types directly from SalesOrder. The procedure now only has to check whether SalesOrder objects passed in were of type ConfirmableSalesOrder, and if so, it could use that type to call the Confirm method:

If TypeOf(order) Is ConfirmableSalesOrder Then
   CType(order, ConfirmableSalesOrder).Confirm

The CType downcasting conversion is still required to get to the Confirm method. But then, through the magic of virtuality, the call automatically passes down to whichever derived class the order was created with and runs the Confirm code defined there.

So, the problem seems to be solved, but this is only a temporary solution. Have you guessed why? Suppose you next need to deal with the fact that some types of orders need a credit check. Some of the orders needing credit checks are also ones that get confirmed, but some aren't. Now you're in trouble.

No Multiple-Inheritance in .NET

You could try creating a CreditCheckableSalesOrder type that derives from SalesOrder. Some order types would derive from ConfirmableSalesOrder, and some from CreditCheckableSalesOrder, but what about those that need both confirmation and credit checks? One of the limitations on inheritance in the .NET Framework is that a type can only be derived from one base type.

You can't have order types that derive from both ConfirmableOrder and CreditCheckableOrder. This may seem like an arbitrary or misguided limitation, but there are good reasons for it. Multiple-inheritance is supported in C++. However, just about all the other modern object-oriented languages, including Java, have chosen not to allow multiple-inheritance. (Some advanced languages, such as Eiffel, have attempted to work out the kinks of multiple inheritance, and Eiffel for Microsoft .NET even provides the appearance of multiple inheritance within Microsoft .NET.)

The biggest problem with multiple-inheritance is that it allows for ambiguity when the compiler needs to find the correct implementation of a virtual method. For example, suppose that Hound and Puppy are both derived from Dog, and then BabyBasset is derived from both Hound and Puppy:

Figure 1. The problem with multiple-inheritance

Now suppose that Dog has an overridable Bark method. Hound overrides it to sound like a howl, Puppy overrides it to sound like a yelp, and BabyBasset doesn't override Bark. If you create a BabyBasset object and call its Bark method, what will you get, a howl or a yelp?

The .NET Framework prevents these kinds of problems by mandating that a derived class can only have a single base class. This limitation also means that every class is ultimately derived from a single great-grand-daddy class, System.Object.

A singly rooted class hierarchy means that any Microsoft .NET object can be handled by a method that takes a parameter of type System.Object. One area where this is crucial is garbage collection, because the garbage collector that releases memory consumed by inaccessible objects must be able to handle all kinds of objects.

A more familiar example of the benefits of allowing all objects to be upcast to a common type is event handlers:

Public Sub MyEventHandler(By Val sender As _
 System.Object, By Val e As System.EventArgs) 

This handler can be hooked up to events from any object or any combination of objects, because the sender parameter is typed as System.Object, and any Microsoft .NET object can be substituted for that. If necessary, you can use System.Reflection.GetType() to discover what type of object the sender is.

Creating and Implementing Interfaces

The subtleties concerning inheritance are very interesting, but what are we going to do about our sales orders that need confirmation and/or a credit-check? The answer is to use interfaces.

The problems caused by multiple inheritance of classes result from potential conflicts among the various implementations of common methods in the inheritance chain. But suppose you knew that the classes you were inheriting from were pure abstract classes with no implementation? In that case, multiple-inheritance wouldn't cause any problems because there would be no implementations to cause conflicts. This is what interfaces provide: a way to inherit just a set of method and property specifications with no implementation to worry about and no problem inheriting from as many interfaces as you need.

Although the phrase "interface inheritance" is often used, the correct term is interface implementation. It is possible for one interface to inherit from another, thereby extending its mandated set of methods to include those of the interface from which it inherits. However, to use interfaces in a Visual Basic .NET class, you implement them rather than inherit them:

Public Interface IConfirmable
  Sub Confirm()
End Interface
Public Class WebSalesOrder()
  Inherits SalesOrder
  Implements IConfirmable
  Public Sub Confirm() Implements IConfirmable.Confirm
    ' Code to confirm a web order
  End Sub
  ' Other WebSalesOrder code
End Class

(In C#, a colon is used to express both class inheritance and interface implementation. This is probably why it is conventional to prefix all interface names with an "I". This allows C# programmers to easily distinguish base classes from interfaces.)

You can create some types of sales orders that implement IConfirmable, some that implement ICreditCheckable, and some that do both. To check whether an order is confirmable, you use the same kind of code you would use to check whether or not it was derived from a particular base type and to downcast it to that type:

Public Sub ProcessOrder(order As SalesOrder)
 If TypeOf(order) Is IConfirmable Then
   CType(order, IConfirmable).Confirm

Interface Polymorphism

You can use interfaces to provide the same benefits of polymorphism that are available when using derived classes. For example, you can pass any object made from a class that implements IConfirmable to a method that takes a parameter of type IConfirmable:

Public Sub ConfirmOrder(order As IConfirmable)
  order.Confirm
End Sub

If WebSalesOrder and EmailSalesOrder both implement IConfirmable, you're able to pass either kind of object to the ConfirmOrder method. When it calls order.Confirm, the confirmation code that was implemented in the appropriate class will run. This works even if you don't name the class method Confirm, as long as you mark it as implementing the Confirm method of the IConfirmable interface:

Public Class WebSalesOrder()
  Inherits SalesOrder
  Implements IConfirmable
  Public Sub ConfirmWebOrder() _
   Implements IConfirmable.Confirm
    ' Code to confirm a web order
  End Sub

This naming freedom is a useful feature. It comes in handy if your class implements two different interfaces that happen to have methods with the same name.

Comparing Class Inheritance and Interface Implementation

The most important technical distinction between creating a derived class and implementing an interface is that a derived class can only inherit from one base class, but a class can implement any number of interfaces.

From a design standpoint, think of inheritance as expressing a specialization hierarchy. If WebSalesOrder "is a" special kind of SalesOrder, you might consider making it a derived class.

However, you need to be very careful that you don't use inheritance when the specialization that distinguishes a derived class from a base class is a feature that other classes will also need to support. For adding those kinds of features or capabilities to a class, implementing interfaces will give you much greater flexibility.

Inheritance is for Building Frameworks

Designing useful inheritance hierarchies takes careful planning and a clear vision of how the hierarchy will be used. Think of it as a task to be performed by an accomplished software architect who is creating a framework that will be used by programmers building a variety of applications, not as a strategy to use when you are simply building a particular application.

The .NET Framework itself includes many examples of inheritance at work, and you will need to create derived classes to perform many common programming activities. For example, you can create your own specialized exception classes derived from ApplicationException to carry customized error information. When you raise custom events, you send information to event handlers by creating a custom class derived from EventArgs. To create a type-specific collection, you'll derive from CollectionBase. And every time you create a Windows form in Visual Studio .NET, you are deriving from the Windows.Forms.Form base class.

You need to be comfortable creating derived classes from base classes provided for you by framework architects, but be very cautious about creating your own base classes. Be sure that you are expressing a clear specialization hierarchy, and that you have clearly factored out the behaviors that you expect client programmers to override.

The same caution applies to creating your own interfaces, but you are much less likely to paint yourself into a corner with interfaces than with inheritance, so give them preference if you feel you have a choice.

Object Composition

When considering the creation of your own inheritance hierarchies, don't be overly influenced by the appeal of code reuse. That alone is not a good reason to create a derived class.

Instead of using inheritance to allow a new object to make use of code from an existing object, you can instead use a technique that goes by several different names: composition, containment, aggregation, or wrapping. This is a technique that you are likely to have used in Visual Basic 6.0, where inheritance wasn't an option.

For example, to create a WebSalesOrder class that reuses all the code in the SalesOrder class and that also adds some new twists, you declare and create an instance of a SalesOrder object in your WebSalesOrder class. You can publicly expose the internal SalesOrder, or you can keep it private.

Delegation

If SalesOrder has a Total method, your WebSalesOrder can have a Total method that simply calls into the Total method of the private SalesOrder instance. This technique of passing along method calls (or property calls) to an internal object is often called delegation, but don't confuse it with the unrelated use of delegate objects to create callback functions and event handlers in Microsoft .NET. The events of your contained objects can be exposed in the wrapper class by declaring the contained object using the WithEvents keyword, just as in Visual Basic 6.0.

Combining Composition with Interface Implementation

The primary disadvantage of using object composition and delegation is that you don't automatically get polymorphism the way you do with derived classes. If your WebSalesOrder object simply contains a SalesOrder object, rather than being derived from one, then you can't pass a WebSalesOrder object to a method that has a parameter of type SaleOrder.

You can easily work around this disadvantage by creating an ISalesOrder interface that your WebSalesOrder implements. You have the option of delegating to the methods and properties of the contained SalesOrder object. Or, as needed, WebSalesOrder can independently implement some of the methods and properties in the interface instead of delegating to the SalesOrder object. This is similar to overriding in derived classes.

By combining object composition and delegation with interface implementation, you get code reuse and polymorphism without the design headaches of inheritance. For this reason, consider this as your first choice when you need to extend or specialize the functionality of classes.

What's Different From Visual Basic 6.0?

In Visual Basic 6.0, every time you create a class module, a hidden interface of the same name is automatically created for you. Consider the following Visual Basic 6.0 code:

' Visual Basic 6.0 code
Dim myObject As MyClass
Set myObject = New MyClass

In this code, the first use of MyClass refers to the hidden interface containing empty methods and properties, and the second MyClass is the concrete class you wrote that implements those methods and properties. Visual Basic 6.0 shelters you from having to think about this use of interfaces, which is essential to the underlying COM plumbing.

The Implements keyword in Visual Basic 6.0 allows you to make explicit use of interfaces and to take advantage of the same interface-based polymorphism that is available in Visual Basic .NET. The implementing class, however, must use a naming convention that incorporates the name of the interface:

' Visual Basic 6.0 IConfirmable class module
Public Sub Confirm()
End Sub

' VB6 WebSalesOrder class module
Implements IConfirmable
Private Function IConfirmable _Confirm()
    ' Implementation code here 
    '  to confirm the order
End Function

Any Visual Basic 6.0 object implementing IConfirmable in this way can be passed to a procedure that takes a parameter of type IConfirmable, supporting the same type of polymorphism that interfaces provide in Visual Basic .NET:

' Visual Basic 6.0 or Visual Basic .NET
Public Sub ConfirmOrder(order As IConfirmable)
  order.Confirm
End Sub

Although the syntax for working with interfaces is more flexible and less confusing in Visual Basic .NET, the biggest change is support for inheritance. Deriving a new class from an old one and picking up the functionality of the old class isn't possible in Visual Basic 6.0, so there is also no way to override only selected methods.

This is a very significant change, but not because you have a burning need to create your own base classes and derive from them. The biggest value comes from being able to inherit the same framework classes used by programmers working in C#, C++, or any other Mirosoft .NET language. And if you do create inheritance-based frameworks yourself in Visual Basic .NET, any other Microsoft .NET programmer can use your frameworks. This puts Visual Basic .NET on equal footing with all the Microsoft .NET languages and breaks down the barriers that have isolated Visual Basic programmers in the past.

Summary

In this document, you learn about the differences between class inheritance and interface implementation. Inheritance supports the creation of hierarchical frameworks of increasingly specialized classes that share some code and also add their own customizations. Interfaces allow multiple unrelated classes to share predictable sets of methods and properties. Both interfaces and inheritance provide polymorphism, allowing generic procedures to work with many different kinds of objects. You also saw how object composition allows you to reuse and extend implementation code without inheritance, and how it can be combined with interfaces to support polymorphism. All these techniques enable you to create and revise complex software systems by helping you add new functionality, with minimal need to dig back into old working code.

About the Author

Andy Baron is a senior consultant with MCW Technologies and is a Microsoft Certified Solution Provider based in Singer Island, Florida. Andy is a regular contributor to technical publications and a regular speaker at industry conferences, He has received Microsoft's Most Valuable Professional (MVP) award every year since 1995. Andy also writes and presents courseware for Application Developers Training Company (www.appdev.com).

About Informant Communications Group

Informant Communications Group, Inc. (www.informant.com) is a diversified media company focused on the information technology sector. Specializing in software development publications, conferences, catalog publishing and Web sites, ICG was founded in 1990. With offices in the United States and the United Kingdom, ICG has served as a respected media and marketing content integrator, satisfying the burgeoning appetite of IT professionals for quality technical information.

Copyright © 2002 Informant Communications Group and Microsoft Corporation

Technical editing: PDSA, Inc. or KNG Consulting.

Show:
© 2014 Microsoft