While the OO features of VB have been very powerful and useful, we have been held back in many cases by the lack of inheritance in the language. Inheritance is the ability of a class to gain the interface and behaviors of an existing class. The process by which this is accomplished is called subclassing. When we create a new class that inherits the interface and behaviors from an existing class, we have created a subclass of the original class. This is also known as an “is-a” relationship, where the new class “is-a” type of original class.
There is a lot of terminology surrounding inheritance – much of it redundant. The original class, from which we inherit interface and behavior is known by the following interchangeable terms:
- Parent class
- Superclass
- Base class
The new class that inherits the interface and behaviors is known by the following interchangeable terms:
- Child class
- Subclass
Inheritance is also sometimes called generalization. In fact this is the term used within the Universal Modeling Language (UML) – the most commonly used object diagramming notation.
Inheritance is often viewed through the lens of biology, where, for example, a dog is a canine and a canine is a mammal. Hence, by being a canine, a dog inherits all the attributes and behavior of a mammal. While useful for visualization, these analogies only go so far.
For interested object-oriented aficionados, VB.NET does not allow multiple inheritance – where a subclass is created by inheriting from more than one parent class. This feature is not supported by the .NET runtime and thus is not available from VB.NET. VB.NET does allow deep inheritance hierarchies where a class is subclassed from a class that is subclassed, but it doesn’t allow a class to be subclassed from multiple parent classes all at once.
We can contrast inheritance, an “is-a” relationship, with another type of parent-child relationship – the “has-a” relationship. This is also known as aggregation or containment.
In a “has-a” relationship, the parent object owns one or more of the child objects, but the child objects are of different types from the parent. For instance, an Invoice has-a LineItem. The LineItem object isn’t subclassed from Invoice – it is an entirely different class that just happens to be owned by the Invoice parent.
This distinction is important, because the terms parent and child are used frequently when working with objects – sometimes when referring to inheritance and sometimes when referring to aggregation. It is important to understand which is which or things can get very confusing.
Within this section, we’ll use the terms parent, child, and subclass – all in the context of inheritance.
Implementing Basic Inheritance
To explore inheritance, consider a business
example with a sales order that has line items. We might have product line items
and service line items. Both are examples of line items, but both are somewhat
different as well. While we could certainly implement ProductLine
and ServiceLine
classes separately, they’d have a lot of common code between them. Redundant
code is hard to maintain, so it would be nicer if they could somehow directly
share the common code between them.
This is where inheritance comes into play.
Using inheritance, we can create a LineItem
class that contains all the code common to any sort of line item. Then we can
create ProductLine
and ServiceLine
classes that inherit from LineItem
– thus automatically gaining all the common code – including interface and implementation
in an OO form.
A simple LineItem
class might appear as:
Public Class LineItem
Private mintID As Integer
Private mstrItem As
String
Private msngPrice As
Single
Private mintQuantity
As Integer
Public Property ID()
As Integer
Get
Return
mintID
End Get
Set
mintID
= value
End Set
End Property
Public Property Item()
As String
Get
Return
mstrItem
End Get
Set
mstrItem
= Value
End Set
End Property
Public Property Price()
As Single
Get
Return
msngPrice
End Get
Set
msngPrice
= Value
End Set
End Property
Public Property Quantity() As Integer
Get
Return mintQuantity
End Get
Set
mintQuantity = Value
End Set
End Property
Public Function Amount() As Single
Return mintQuantity * msngPrice
End Function
End
Class
This class has things common to any line item – some basic data fields and a method to calculate the cost of the item.
If a line item is for a product, however,
we might have additional requirements. The Item
value should probably be validated to make sure it refers to a real product,
and perhaps we want to provide a product description as well:
Public
Class ProductLine
Inherits LineItem
Private mstrDescription As String
Public ReadOnly Property Description() As String
Get
Return mstrDescription
End Get
End Property
Public Sub New(ByVal ProductID As String)
Item = ProductID
‘ load product data from database
mstrDescription = “Test product description”
End Sub
End
Class
Note the use of the
Inherits
statement.
Inherits LineItem
It is this statement that causes the ProductLine
class to gain all the interface elements and behaviors from the LineItem
class. This means that we can have client code like this:
Protected
Sub Button1_Click(ByVal
sender As Object, _
ByVal e As System.EventArgs)
Dim pl As ProductLine
pl = New ProductLine(“123abc”)
MessageBox.Show(pl.Item)
MessageBox.Show(pl.Description)
End
Sub
This code makes use of both the Item
property (from the LineItem
class) and the Description
property from the ProductLine
class. Both are equally part of the ProductLine
class, since it is a subclass of LineItem
.
Likewise, a line item for a service might have a date for when the service was provided, but otherwise be the same as any other line item:
Public Class ServiceLine
Inherits LineItem
Private mdtDateProvided
As Date
Public Sub New()
Quantity
= 1
End Sub
Public Property DateProvided()
As Date
Get
Return
mdtDateProvided
End Get
Set
mdtDateProvided
= Value
End Set
End Property
End Class
Again, notice the use of the Inherits
statement that indicates this is a subclass of the LineItem
class. The DateProvided
property is simply added to the interface gained from the LineItem
class.
Preventing Inheritance
By default any
class we create can be used as a base class from which other classes can be
created. There are times when we might want to create a class that cannot be
subclassed. To do this we can use the NotInheritable
keyword in our class declaration:
Public NotInheritable
Class ProductLine
End Class
When this keyword is used, no other code
may use the Inherits
keyword to create a subclass of our class.
Inheritance and Scoping
When
we create a subclass through inheritance, the new class gains all the
Public
and Friend
methods, properties, and variables from the original class. Anything declared
as Private
in the original class will not be directly available to our code in the new
subclass.
The exception to this is the
New
method. Constructor methods must be re-implemented in each subclass. We’ll discuss
this in more detail later in the chapter.
For instance, we might rewrite the Amount
methods from the LineItem
class slightly:
Public
Function Amount() As Single
Return CalcAmount
End
Function
Private
Function CalcAmount() As Single
Return fQuantity * fPrice
End
Function
With this change, we can see that the
Public
method Amount
makes use of a Private
method to do its work.
When we subclass LineItem
to create the ServiceLine
class, any ServiceLine
object will have an Amount
method because it is declared as Public
in the base class. The CalcAmount
method, on the other hand, is declared as Private
and so neither the ServiceLine
class nor any client code will have any access to it.
Does this mean the Amount
method will break when called through the ServiceLine
object? Not at all. Since the Amount
method’s code resides in the LineItem
class, it has access to the CalcAmount
method even though the ServiceLine
class can’t see the method.
For instance, in our client code we might have something like this:
Protected
Sub Button1_Click(ByVal
sender As Object, _
ByVal e As System.EventArgs)
Dim sl As ServiceLine
sl = New ServiceLine()
sl.Item = “delivery”
sl.Price = 20
sl.DateProvided = Now
MsgBox(sl.Amount, MsgBoxStyle.Information, “Amount”)
End
Sub
The result is displayed in a message box,
thus illustrating that the CalcAmount
method was called on our behalf even though neither our client code, nor the
ServiceLine
code directly made the call.
Protected Methods
Sometimes Public and Private aren’t enough. If we declare something as Private it is totally restricted to our class, while if we declare something as Public (or Friend) it is available to both subclasses and client code. There are times when it would be nice to create a method that is available to subclasses, but not to client code.
This is where the Protected
scope comes into play. When something is declared as Protected
,
it is not available to any code outside of the class. However, it is
available to classes that are derived from our class through inheritance.
For example:
Public
Class ParentClass
Protected TheValue As Integer
End
Class
Public
Class SubClass
Inherits ParentClass
Public Function GetValue() As Integer
Return TheValue
End Function
End
Class
Here we have a parent class with a Protected
member – TheValue
.
This variable is not available to any client code. However, the variable is
fully available to any code within SubClass
,
because it inherits from the parent.
In this example, SubClass
has a Public
method that actually does return the protected value – but the variable TheValue
is not directly available to any client code (that is, code outside the class).
Overriding Methods
One
key attribute of inheritance is that a subclass not only gains the behaviors
of the original class, but it can also override those behaviors. We’ve already
seen how a subclass can extend the original class by adding new Public
,
Protected
,
and Friend
methods. However, by using the concept of overriding, a subclass can alter
the behaviors of methods that were declared on the parent class.
By default, methods cannot be overridden
by a subclass. To allow them to be overridden, the parent class must declare
the method using the Overridable
keyword:
Public
Class Parent
Public Overridable Sub DoSomething()
MessageBox.Show(“Hello from Parent”)
End Sub
End
Class
We can also explicitly disallow overriding
through the use of the NotOverridable
keyword.
Of course since this is the default, this keyword
is rarely used.
However, it may be a good practice to explicitly define whether a method can or cannot be overridden to increase the clarity of code and to protect against the possibility that the default behavior might someday change.
If we then create a subclass, we can optionally
override the behavior of DoSomething
by using the Overrides
keyword:
Public
Class SubClass
Inherits Parent
Public Overrides Sub DoSomething()
MessageBox.Show(“Hello from SubClass”)
End Sub
End
Class
Now we can write client code such as:
Dim
obj As New SubClass()
obj.DoSomething()
The result will be a message dialog containing
the text Hello
from SubClass. This isn’t surprising – after all,
we overrode the DoSomething
method with our new code.
Virtual Methods
However, consider the following client code:
Dim
obj As Parent
obj
= New SubClass()
obj.DoSomething()
First off, it seems odd to declare a variable
of type Parent
,
but then create a SubClass
object instead. This is perfectly acceptable however – it is yet another way
of implementing polymorphism. Since SubClass
“is-a” Parent
,
any Parent
orSubClass
variable can hold a reference to a SubClass
object.
This is true in general. When using inheritance, a variable of the parent type can always hold references to any child type created from the parent.
What may be more surprising is the message that is displayed in our message box when this code is run. The message we see is Hello from SubClass.
How can this be? The variable is declared
as type Parent
– shouldn’t the Parent
implementation be called? The reason the DoSomething
implementation from the child class is called is that the method is virtual.
The concept of a virtual method is such that the “bottom-most” implementation
of the method is always used in favor of the parent implementation – regardless
of the data type of the variable being used in the client code.
Unlike
many object-oriented languages, all methods in VB.NET are virtual.
The term “bottom-most” comes from the typical way a chain of inherited objects is diagrammed. Usually the parent class is displayed, with the subclasses underneath. If the subclasses are also subclassed, then those classes are shown even further down. This is illustrated by the following UML diagram:
|
Regardless of the variable data
type, the implementation of DoSomething
will be invoked based on the actual class we use to create the object. In our
previous example, we created an object of type SubClass
,
thus ensuring that the DoSomething
implementation in that class would be invoked. This is illustrated by the following
diagram, which shows how the method calls are made:
|
If we create an object from type Parent
with the following code:
Dim obj As Parent
obj = New Parent()
obj.DoSomething()
The DoSomething
implementation in the Parent
class will be invoked since that is the type of object we created as shown in
the following diagram:
|
We can also create the object from the
SubSubClass
class:
Dim obj As Parent
obj = New SubSubClass()
obj.DoSomething()
In this case, the class doesn’t directly
implement DoSomething
,
so we start looking back up the inheritance chain:
|
The first class up that chain is SubClass
,
which does have an implementation – so it is that implementation which
is invoked. No code from the Parent
class is invoked at all.
Me Keyword
The Me
keyword is used any time we want our code to refer to methods within the current
object. This was used in VB6 – when we might have utilized the Me
keyword to refer to the current form, or the current instance of an object –
and the same is true in VB.NET.
The Me
keyword is analogous to the this
keyword in C++ and C# languages.
The Me
keyword is usually optional, since any method call is assumed to refer to the
current object unless explicitly noted otherwise. The exception is when we are
working with shadowed
variables.
A shadowed variable is a procedure-level variable with the same name as a class-level variable. For instance:
Public
Class TheClass
Private strName As String
Public Sub DoSomething()
Dim strName As String
strName = “Fred”
End Sub
End
Class
The variable strName
is declared at the class level and within the DoSomething
method. Within that method only the local, or shadowed, variable is used unless
we explicitly reference the class-level variable with the Me
keyword:
Public Sub DoSomething()
Dim strName As String
strName = “Fred” ‘ sets the local variable’s
value
Me.strName = “Mary” ‘ sets the class level variable’s value
End Sub
Here we can see that strName
can be used to reference the local variable, while Me.strName
can be used to reference the class-level variable.
As useful as the Me
keyword can be for referring to the current object, when we start working with
inheritance, it isn’t enough to do everything we want.
There are two issues we need to deal with. Sometimes we may want to explicitly call into our parent class – and sometimes we may want to ensure that the code in our class is being called, rather than the code in some subclass that has inherited our code.
MyBase Keyword
At times we might want to explicitly call into methods in our parent class. Remember that when we override a method it is virtual – so the method call will invoke the “bottom-most” implementation – not the parent implementation. However, sometimes we might need that parent implementation.
To invoke the parent class from within
our subclass we can use the MyBase
keyword. For instance:
Public
Class SubClass
Inherits Parent
Public Overrides Sub DoSomething()
MessageBox.Show(“Hello from subclass”)
MyBase.DoSomething()
End Sub
End
Class
If we run our client code now, we’ll get two message boxes. First we’ll get the message from the subclass, followed by the one from the parent class.
The MyBase
keyword can be used to invoke or use any Public
,
Friend
,
or Protected
element from the parent class. This includes all of those elements directly
on the base class, and also any elements the base class inherited from other
classes higher in the inheritance chain.
MyBase
only refers to the immediate parent of the current class. If we create
a SubSubClass
that inherits from SubClass
,
the MyBase
keyword would refer to the SubClass
code, not the Parent
code. There is no direct way to navigate more than one level up the inheritance
chain.
MyClass Keyword
A more complex scenario is one where the code in our class might end up invoking code from other classes created from our class.
When we create a class, we’ll frequently make calls from within our class to other methods within that same class. This occurs in the following code:
Public Class Parent
Public Sub DoSomething()
OtherStuff()
End Sub
Public Overridable
Sub OtherStuff()
MessageBox.Show(“Parent
other stuff”)
End Sub
End Class
In this case, the DoSomething
method calls the OtherStuff
method to do some work. Notice however, that OtherStuff
is marked as Overridable
,
so a subclass might provide a different implementation for the method. For example:
Public
Class SubClass
Inherits Parent
Public Overrides Sub OtherStuff()
MessageBox.Show(“SubClass other stuff”)
End Sub
End
Class
As we discussed earlier, VB.NET methods
are virtual – which means that an object of type SubClass
will always invoke OtherStuff
from SubClass
rather than from the Parent
class. This is true even for code in the Parent
class itself – so when the DoSomething
method calls the OtherStuff
method it will invoke the overridden implementation in SubClass
.
This can be illustrated by the following client code:
Dim
obj As New SubClass()
obj.DoSomething()
We will see a dialog displaying SubClass
other stuff. The DoSomething
method in the Parent
class invokes the overridden OtherStuff
implementation within the SubClass
class.
If we don’t want this behavior – if we
want the code in the Parent
class to know for certain that it is calling the OtherStuff
implementing from Parent
– we need to use the MyClass
keyword:
Public
Class Parent
Public Sub DoSomething()
MyClass.OtherStuff()
End Sub
Public Overridable Sub OtherStuff()
MessageBox.Show(“Parent other stuff”)
End Sub
End
Class
We can’t use the Me
keyword, because that will reference the virtual method. MyClass
,
on the other hand, forces the call to be handled by the code in the same class
as the call – in this case the Parent
class.
This example is illustrated by the following diagram, which shows the method calls being made:
|
Here we can see that the client calls
DoSomething
,
which is actually invoked from the Parent
class. The Parent
class then calls OtherStuff
,
but since it is implemented in SubClass
,
that is the implementation that is invoked.
Overriding the Constructor Method
We’ve
already seen how we can override methods, and how to use the Me
,
MyBase
,
and MyClass
keywords to interact with the various overridden methods in our inheritance
chain. However, there are special rules that govern the process of overriding
the New
constructor method.
New methods aren’t automatically carried from a parent to a subclass like normal methods. Each subclass must define its own constructors, though those constructors may call into the parent class using the MyBase keyword:
Public
Class SubClass
Inherits Parent
Public Sub New()
MyBase.New()
‘ other initialization code goes here
End Sub
End
Class
When calling the base class constructor, that call must be the first line in our constructor code – anything else is an error. This is totally optional, however, since the constructor of the parent class is automatically called on our behalf before our constructor code begins to run, unless we make that call manually.
If all constructor methods of the base class require
parameters then we must implement at least one constructor in our subclass and
we must explicitly call MyBase.New from within our constructors.
As we discussed earlier, the New
method can be overloaded, providing various implementations. If our parent class
provides alternate implementations of New
,
we may want to manually make the call in order to cause the correct implementation
to be called, based on our requirements.
Creating Base Classes and Abstract Methods
So far, we’ve seen how to inherit from a class, how to overload and override methods, and how virtual methods work. In all of our examples so far, the parent classes have been useful in their own right. Sometimes however, we want to create a class such that it can only be used as a base class for inheritance.
MustInherit Keyword
Returning to
our original sales order line item example, it may make little sense for anyone
to create an object based on the generic LineItem
class. In fact, we may want to ensure that only more specific subclasses derived
from LineItem
can be created. What we want to create is something called a base
class. This is done using the MustInherit
keyword in the class declaration:
Public MustInherit Class
LineItem
Typically, no other change is required
in our class. The result of this keyword is that it is no longer possible to
write client code that creates an instance of the LineItem
class, so the following would cause a syntax error:
Dim obj As New LineItem()
Instead,
to use the code in the LineItem
class, we must create subclasses and use those throughout
our application.
MustOverride Keyword
Another option
we have is to create a method that must be overridden by a subclass. We might
want to do this when we are creating a base class that provides some behavior,
but relies on subclasses to also provide some behavior in order to function
properly. This is accomplished by using the MustOverride
keyword on a method declaration:
Public MustOverride
Sub CalcPrice()
Notice that there is no End
Sub
or any other code associated with the method.
When using MustOverride
,
we cannot provide any implementation for the method in our class. Such
a method is called an abstract
method or pure
virtual function, since it only defines the interface
and no implementation.
Methods declared in this manner must be overridden in any subclass that inherits from our base class. If we don’t override one of these methods, we’ll generate a syntax error in the subclass and it won’t compile.
Abstract Base Classes
We
can combine these two concepts – using both MustInherit
and MustOverride
– to create something called an abstract
base class. This is a class that provides no implementation,
only the interface definitions from which a subclass can be created. An example
might be as follows:
Public
MustInherit Class Parent
Public MustOverride Sub DoSomething()
Public MustOverride Sub DoOtherStuff()
End
Class
This technique can be very useful when
creating frameworks or the high-level conceptual elements of a system.
Any
class that inherits Parent
must implement both DoSomething
and DoOtherStuff
or a syntax error will result.
In some ways an abstract base class is
very comparable to defining an interface using the Interface
keyword. We’ll discuss the Interface
keyword in detail later in this chapter. For now, be aware that the Interface
keyword is used to formally declare an interface that can be implemented using
the Implements
keyword as in VB6.
We could define the same interface as shown in this example with the following code:
Public
Interface IParent
Sub DoSomething()
Sub DoOtherStuff()
End
Interface
Any class that implements the IParent interface must implement both DoSomething and DoOtherStuff or a syntax error will result – and in that regard this technique is similar to an abstract base class.
There are differences however. In particular,
when we create a new class by subclassing the Parent
class, that class can in turn be subclassed. Those classes will automatically
have DoSomething
and DoOtherStuff
methods due to the nature of inheritance.
Compare this with the interface approach,
where each individual class must independently implement the IParent
interface and provide its own implementation of the two methods. If we
never intend to reuse the code that implements these methods as we create new
classes then the interface approach is fine, but if we want code reuse within
subclasses inheritance is the way to go.
Comments