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
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
Code language: PHP (php)
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
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.