In this section, you’ll build a few classes to represent shapes to demonstrate the advantages of implementing polymorphism. Let’s start with the example Shape class which we are going to discuss here, which will be the base class for all other shapes. This is a really simple class that’s pretty useless on its own. Its real use is to expose two methods that can be inherited: Area and Perimeter. Even the two methods don’t do much — actually, they do absolutely nothing. All they really do is provide a naming convention. All classes that will inherit the Shape class will have an Area and a Perimeter method, and they must provide the implementation of these methods.
The code shown in Listing 7.8 comes from the Shapes sample project. The application’s main form, which exercises the Shape class and its derived classes, is shown in Figure 7.3.
Figure 7.3 – The main form of the Shapes project
Listing 7.8: Shape Class
Public Class Shape
Overridable Function Area() As Double
End Function
Overridable Function Perimeter() As Double
End Function
End Class
Code language: JavaScript (javascript)
If there are properties common to all shapes, you place the appropriate Property procedures in the Shape class. If you want to assign a color to your shapes, for instance, insert a Color property in this class. The Overridable keyword means that a class that inherits from the Shape class can override the default implementation of the corresponding methods or properties. As you will see shortly, it is possible for the base class to provide a few members that can’t be overridden in the derived class. The methods that are declared but not implemented in the parent class are called virtual methods, or pure virtual methods.
Next you must implement the classes for the individual shapes. Add another Class module to the project, name it Shapes, and enter the code shown in Listing 7.9.
Listing 7.9: Square, Triangle, and Circle Classes
' Triangle Class
Public Class Triangle
Inherits Shape
Private side1, side2, side3 As Double
Sub New(ByVal sideA As Double, ByVal sideB As Double, ByVal sideC As Double)
MyBase.New()
side1 = sideA
side2 = sideB
side3 = sideC
End Sub
Sub New()
End Sub
Property SideA() As Double
Get
SideA = side1
End Get
Set(ByVal Value As Double)
side1 = Value
End Set
End Property
Property SideB() As Double
Get
SideB = side2
End Get
Set(ByVal Value As Double)
side2 = Value
End Set
End Property
Public Property SideC() As Double
Get
SideC = side3
End Get
Set(ByVal Value As Double)
side3 = Value
End Set
End Property
Public Overrides Function Area() As Double
Dim Perim As Double
Perim = Perimeter()
Return (Math.Sqrt((Perim - side1) * (Perim - side2) * (Perim - side3)))
End Function
Public Overrides Function Perimeter() As Double
Return (side1 + side2 + side3)
End Function
End Class
' Circle Class
Public Class Circle
Inherits Shape
Private cRadius As Double
Sub New(ByVal radius As Double)
MyBase.New()
cRadius = radius
End Sub
Sub New()
End Sub
Public Property Radius() As Double
Get
Radius = cRadius
End Get
Set(ByVal Value As Double)
cRadius = Value
End Set
End Property
Public Overrides Function Area() As Double
Return (Math.PI * cRadius ^ 2)
End Function
Public Overrides Function Perimeter() As Double
Return (2 * Math.PI * cRadius)
End Function
End Class
' Square Class
Public Class Square
Inherits Shape
Private sSide As Double
Sub New(ByVal Side As Double)
MyBase.New()
sSide = Side
End Sub
Sub New()
End Sub
Public Property Side() As Double
Get
Side = sSide
End Get
Set(ByVal Value As Double)
sSide = Value
End Set
End Property
Public Overrides Function Area() As Double
Area = sSide * sSide
End Function
Public Overrides Function Perimeter() As Double
Return (4 * sSide)
End Function
End Class
The Shapes.vb file contains three classes: the Square, Triangle, and Circle classes. All three expose their basic geometric characteristics as properties. The Triangle class, for example, exposes the properties SideA, SideB, and SideC, which allow you to set the three sides of the triangle. In a real-world application, you may opt to insert some validation code, because not any three sides produce a triangle. You must also insert parameterized constructors for each shape. The implementation of these constructors is trivial, and I’m not showing it in the listing; you’ll find the appropriate constructors if you open the project with Visual Studio. The Area and Perimeter methods are implemented differently for each class, but they do the same thing: They return the area and the perimeter of the corresponding shape. The Area method of the Triangle class is a bit involved, but it’s just a formula (the famous Heron’s formula for calculating a triangle’s area).
Testing the Shape Class
To test the Shape class, all you have to do is create three variables — one for each specific shape — and call their methods. Or, you can store all three variables into an array and iterate through them. If the collection contains Shape variables only, the current item is always a shape, and as such it exposes the Area and Perimeter methods. The code in Listing 7.10 does exactly that. First, it declares three variables of the Triangle, Circle, and Square types. Then it sets their properties and calls their Area method to print their areas.
Listing 7.10: Testing the Shape Class
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim shape1 As New Triangle()
Dim shape2 As New Circle(4)
Dim shape3 As New Square(10.01)
' SET UP TRIANGLE
shape1.SideA = 3
shape1.SideB = 3.2
shape1.SideC = 0.94
Dim msg As String
msg = "The triangle’s area is " & shape1.Area
msg = msg & vbCrLf & "The circle’s area is " & shape2.Area
msg = msg & vbCrLf & "The square’s area is " & shape3.Area
MsgBox(msg)
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
Dim aList As New ArrayList()
aList.Add(New Triangle(3, 3.2, 0.94))
aList.Add(New Square(10.01))
aList.Add(New Circle(4))
Dim shapeEnum As IEnumerator
Dim totalArea As Double
shapeEnum = aList.GetEnumerator
While shapeEnum.MoveNext
totalArea = totalArea + CType(shapeEnum.Current, Shape).Area
End While
MsgBox("The total area of the three shapes is " & totalArea)
End Sub
Code language: PHP (php)
In the last section, the test code stores all three variables into an array and iterates through its elements. At each iteration, it casts the current item to the Shape type and calls its Area method. The expression that calculates areas is CType(shapeEnum.Current, shape).Area, and the same expression calculates the area of any shape.
Depending on how you will use the individual shapes in your application, you can add properties and methods to the base class. In a drawing application, all shapes have an outline and a fill color. These properties can be implemented in the Shape class because they apply to all derived classes. Any methods with a common implementation for all classes should also be implemented as methods of the parent class. Methods that are specific to a shape must be implemented in one of the derived classes.
Casting Objects to Their Parent Type
The trick that makes polymorphism work is that objects of a derived type can be cast to their parent type. An object of the Circle type can be cast to the Shape type, because the Shape type contains less information than the Circle type. You can cast objects of a derived type to their parent type, but the opposite isn’t true. The methods that are shared among multiple derived classes should be declared in the parent class, even if they contain no actual code. Just don’t forget to prefix them with the Overridable keyword. There’s another related attribute, the MustOverride attribute, which forces every derived class to provide its own implementation of a method or property.