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:
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.
Continued...