Inheriting Classes

Inheritance is a big subject. VB .NET supports single inheritance. This means that a new class can inherit an existing class. This can happen over and over again, layering in new capabilities and functionality every time a new class is created from an existing class. In fact, the entire Common Language Runtime (CLR) inherits from at least one other class, the Object class.

We need to discuss several things so you can get the greatest benefit from inheritance. A good place to start is terminology. Several terms are associated with inheritance, and using them in discourse promotes concise communication. Unfortunately several terms are synonyms, which lends to confusion. Let's take a minute to review the terminology associated with inheritance relationships; then we'll proceed from there.

Inheritance Terminology

As mentioned above, talking about inheritance can be a little confusing because there are several synonyms. However, using appropriate synonyms adds richness to our discussion as long as we are not introducing unnecessary confusion.

When referring to a class relative to the class it inherits from, it is acceptable to use any of the following terms: subclass, child class , or derived class . When referring to a class inherited from, any of the following terms are acceptable: superclass, parent class , or base class . These terms are used in synonymous pairs. Generally we pair parent and child, superclass and subclass, and base class and derived class. Probably the easiest relationship to picture is that of parent and child. You are the child of your mother and father, either of whom is your parent. If you have children, relative to your child you are the parent.

I could stick to one pair of terms, perhaps parent and child, but this is an advanced book and it is unlikely that you will encounter only one set of terms in the great wide world. Thus I will use the terms that sound best in a particular context, just as you would expect anyone else you are communicating with to do.

Occasionally you will hear terms such as grandparent or sibling (seldom uncle or second cousin ). The meanings of these terms are the same as their understood meanings when it comes to personal relationships. A grandparent is the parent of a class's parent. A sibling is a class that has the same parent as another class. Grandparent and sibling are used less frequently than parent and child , but you should understand their meanings.

Using a variety of terms adds richness and precision to communication. The terms mentioned here weren't introduced to add confusion; rather, they were added to eliminate confusion. Probably the first person who used parent and child did so because superclass and subclass did not seem clear to the audience. Reasonable synonyms were picked by the speaker. By reading this book you will learn to use inheritance terms by encountering them in context. If you are still confused about terminology, post a sticky note on your computer and where you will see it while reading until you finish the book. By then you will have mastered the verbiage.

The Inherits Statement

There are two kinds of inheritance in VB .NET: inheritance from classes and inheritance from interfaces. Whether you are inheriting a class or an interface, you apply the idiom the same way. Add a statement using the keyword Inherits followed by the name of the class or interface you want to inherit from.

In this section we focus on class inheritance and its related implications. Refer to the Inheriting Interfaces section later in this chapter for information on inheriting from interfaces.

Every time you add a Windows Form to a Windows application you are inheriting from the System.Windows.Forms.Form class. Look at the code behind a Windows Form. The Inherits statement in a Windows Form is identical to how you will indicate inheritance for any class. The only thing that will change is the name of the thing you are inheriting from. Here is an excerpt from a Windows Form indicating inheritance.

 Public Class Form1   Inherits System.Windows.Forms.Form 

As shown, the inheritance statement immediately follows the class header ( Public Class Form1 ). The class header begins a class definition and, in this case, indicates we are talking about a public class named Form1 . (The keyword Public is an access modifier. Refer to the Access Modifiers section in this chapter for more information.) The Inherits statement in the excerpt above uses the fully qualified namespace path and the class. All you have to specify in the Inherits statement is the class. Assuming the System.Windows.Forms namespace is imported, the Inherits statement could be rewritten as follows:

 Inherits Form 

When a derived class is created, it inherits all the members of the base class. There is an exact one-to-one mapping between the members in a base class and those in a derived class until you add new members to the derived class. And that is precisely a reason for creating a derived class: to add new members to an existing class.

Other reasons for deriving a new class ”remember, when I say derive think inherit from ” are to extend or change the behavior of an existing base class. The basic idea is that you need a class to fulfill a need. If you are fortunate you will discover a class that is pretty close to what you want. You can then inherit from this class and add new members or modify existing members to make the new class precisely what you need. (In Chapter 1 we discussed how to add members to a class.)

Let's take a moment to go over the NotInheritable modifier; then we will explore how to extend existing members.

The NotInheritable Modifier

You can use the NotInheritable modifier to terminate the branch of an inheritance hierarchy. Modifiers like NotInheritable are used in the class header. Suppose we have a derived shape class for an ellipse and derived from that class a circle. If we want to terminate inheritance at the Circle class, we could define the class header with the NotInheritable modifier.

 Public Class NotInheritable Circle 

All classes are inheritable unless marked with the NotInheritable modifier.

Abstract Classes

The opposite of NotInheritable classes are classes that must be inherited from before they can be used. In the object-oriented vernacular, such classes are referred to as abstract classes .

Abstract classes are closely related to interfaces. Abstract classes have members that must be implemented just like interfaces. The biggest difference between abstract classes and interfaces is that you can inherit some implementation from abstract classes but interfaces contain no implementation. Use abstract classes when derivatives are part of the same family, such as the shape, ellipse, and circle example in the previous section. Use interfaces when the capabilities span disparate kinds of objects.

You indicate that a class is an abstract class by adding the MustInherit modifier. (Clearly MustInherit and NotInheritable are mutually exclusive.) Listing 2.1 shows the Shape class mentioned earlier implemented as an abstract class.

Listing 2.1 Defining an Abstract Class by Using the MustInherit Modifier
 Public MustInherit Class Shape   Private FRect As System.Drawing.Rectangle   Public Sub New()   End Sub   Public Sub New(ByVal Rect As System.Drawing.Rectangle)     FRect = Rect   End Sub   Public MustOverride Sub Draw(ByVal G As Graphics)   Protected Overridable Function GetRect() As System.Drawing.Rectangle     Return FRect   End Function End Class 

The first line of text in the listing indicates that Shape is an abstract class. We can provide some implementation, but Visual Basic .NET does not allow us to create an instance of the Shape class. This is reasonable too. Consider the problem of implementing the Draw method; without knowing what kind of shape we are talking about, how would we render the shape?

NOTE

As an advanced developer you know that you could implement the Shape class and use an enumeration to determine what kind of shape to draw. For example, based on a ShapeType property you could write condition logic in the Draw method to render the correct kind of shape. Interestingly enough, this is precisely the kind of scenario in which you would want to use derivative classes and a polymorphic drawing behavior to eliminate the conditional code.

Using polymorphism is generally considered a best practice, but there is no enforcement agency. Our discussion illustrates what is so challenging about writing software; the decision-making process is highly subjective .

The MustOverride Modifier

Abstract classes (those defined with the MustInherit modifier) are expected to have at least one abstract member. Abstract members are those that must be overridden in a derived class.

Listing 2.1 defines the Draw method as an abstract method. This is clear by the presence of the MustOverride modifier in the subroutine header and the absence of a method body.

Every derivative class will have to provide an implementation of the Draw method or they themselves will be abstract. If a derivative class does not implement abstract methods , you must add the MustInherit modifier to the class header.

The Overridable Modifier

If you want to allow a consumer to extend the behavior of a method, you need to create a virtual method. Virtual methods in Visual Basic .NET are designated as such by adding the Overridable modifier to the procedure header.

The GetRect method in Listing 2.1 is a virtual method. Derivative classes can extend the behavior of Shape.GetRect by implementing a new version of GetRect and using the Overrides modifier. (See the Overriding Methods and Properties subsection for examples and more discussion.)

The NotOverridable Modifier

If you want to terminate a particular method so that derivative classes do not further extend the behavior of that method, add the NotOverridable modifier to the procedure header.

Assume we define a class derived from Shape in Listing 2.1. We could override the GetRect method and add the NotOverridable modifier to terminate further extension, as shown below.

 Protected NotOverridable Overrides Function GetRect() As System.Drawing.Rectangle 

Children containing the preceding method would not be able to further customize the GetRect method.

Overriding Methods and Properties

As implied in the previous section, extending or replacing the behavior of methods and properties in a derived class is referred to as overriding . Use the Overrides modifier to indicate you are extending the behavior of a virtual member in a base class. Overridden members can invoke the method in the parent class to provide part of the solution or to completely replace the behavior in the parent class.

If the parent class member you are overriding is a virtual method ”that is, it is declared with the MustOverride modifier ”you cannot call the parent class's version of the method. If the parent method is declared with the Overridable modifier, in the derived version you can invoke the parent method or not, as you choose. Listing 2.2 demonstrates a Rectangle class derived from Shape .

Listing 2.2 Implementing a Rectangle Class Derived from the Shape Abstract Class
 Public Class Rectangle   Inherits Shape   Public Sub New(ByVal Rect As System.Drawing.Rectangle)     MyBase.New(Rect)   End Sub   Public Overrides Sub Draw(ByVal G As Graphics)     G.DrawRectangle(Pens.Red, GetRect())   End Sub End Class 

The Draw method in Listing 2.2 overrides the abstract method used in the Shape class. Since the Draw method in Listing 2.1 is purely abstract, we cannot call Draw in Listing 2.1.

Calling Base Class Methods

Sometimes you will want to invoke the behavior in a base class. For example, when you overload a Sub New constructor, you need to call the base class constructor before doing anything else in the derived class constructor.

The reserved keyword MyBase is used to refer to members in a base class. Listing 2.2 demonstrates calling the Sub New constructor in the Shape base class. The relevant portion is extracted below.

 Public Sub New(ByVal Rect As System.Drawing.Rectangle)   MyBase.New(Rect) End Sub 

TIP

The MyBase keyword cannot be used to get around encapsulation rules. For instance, MyBase will not allow you to interact with private members in a base class.

Any time you want to refer to an inherited member, prefix the reference with the MyBase keyword and the member-of operator. It goes without saying that this applies to all inherited base members.

Comparing the Me and MyClass Keywords

The keyword Me is an object that is an internal reference to self. ( Me has the same function as this in C++ and C# and self in Delphi.) You can use the Me reference to self so that the Integrated Development Environment (IDE) will display the Intellisense list of members for an object, or you can use it to clarify intent. For example, if a method has an argument whose name is identical to that of a field, you can use Me to clarify which is the field and which is the argument.

Another keyword, MyClass , does not represent an object but is used internally to clarify intent. When you use MyClass with the member-of dot operator ( . ), you are indicating that you want to access the member of the containing class. MyClass keeps member access from working in a polymorphic way. MyClass causes method access to be treated as if that member were implemented with the NotOverridable modifier. Listing 2.3 helps illustrate the differences between Me and MyClass .

Listing 2.3 Using the Me and MyClass Keywords
 1:  Imports System.Drawing.Drawing2D 2: 3:  Public Class Form1 4:      Inherits System.Windows.Forms.Form 5: 6:  [ Windows Form Designer generated code ] 7: 8:    Private Sub Button1_Click(ByVal sender As System.Object, _ 9:      ByVal e As System.EventArgs) Handles Button1.Click 10:     Refresh() 11:     Dim P As Polygon = New Polygon() 12:     P.Draw(CreateGraphics) 13:   End Sub 14: 15:   Private Sub Button2_Click(ByVal sender As System.Object, _ 16:     ByVal e As System.EventArgs) Handles Button2.Click 17:     Refresh() 18:     Dim P As ChildPolygon = New ChildPolygon() 19:     P.Draw(CreateGraphics) 20:   End Sub 21: End Class 22: 23: Public Class Polygon 24: 25:   Public Overridable Function GetPoints() As PointF() 26: 27:     Dim Path As New GraphicsPath() 28:     Path.AddString("Parent", New FontFamily("Arial"), _ 29:       0, 20, New Point(10, 10), New StringFormat()) 30: 31:     Return Path.PathPoints 32: 33:   End Function 34: 35:   Public Overridable Sub Draw(ByVal G As Graphics) 36:     G.DrawPolygon(Pens.Blue, GetPoints()) 37:   End Sub 38: 39: End Class 40: 41: Public Class ChildPolygon 42:   Inherits Polygon 43: 44:   Public Overrides Function GetPoints() As PointF() 45:     Dim Path As New GraphicsPath() 46:     Path.AddString("Child", New FontFamily("Arial"), _ 47:       0, 20, New Point(10, 10), New StringFormat()) 48: 49:     Return Path.PathPoints 50:   End Function 51: 52: End Class 

Listing 2.3 contains the code from the form in MyClassDemo.sln . The code contains three classes. The first class is the Form class; the remaining two classes are a parent and child class combination that draws a polygon based on a B zier curve. The first 22 lines define the form and contain two button-click event handlers. Button1 instantiates the Polygon class and invokes the Draw method. Button2 instantiates the ChildPolygon class and invokes Draw too. In this discussion we will focus on lines 35 through 37 in the Polygon class, specifically line 36 and how it behaves if we use MyClass or Me .

TIP

Pierre B zier invented B zier curves for the automotive industry. B zier curves are drawn by connecting points and can yield very complex curves.

NOTE

Listing 2.3 demonstrates some extra tidbits that will be helpful for you to know. Line 6 represents code that has been compressed by a Region directive. The Region directive supports custom code outlining, which is a good organization tool.

A second useful point is that modules no longer define the boundaries of types in VB .NET. As you can see in the listing, three classes are defined in the same module. The class header and End Class statement explicitly define the boundaries of a class.

Line 36 invokes the Graphics.DrawPolygon method. More importantly, it calls the GetPoints method. GetPoints is defined in the Polygon and ChildPolygon classes as a virtual method. Whenever we invoke a method internally, it is the same as if we prefixed that method with Me . For example, calling GetPoints on line 36 is exactly the same as calling Me.GetPoints .

When we invoke Me.GetPoints ”implicitly, on line 36 ”we are instructing the method to be invoked polymorphically. That is, we are expecting GetPoints to be called based on the type of the object Me . When Me is a Polygon object, Polygon.GetPoints is called. When Me is a ChildPolygon object, ChildPolygon.GetPoints is called. As a result, Button1 draws the B zier curve defined by the string "Parent" and Button2 draws the curve described by the string "Child" . Normally this is what you want: polymorphic behavior controlled by the actual type of the object.

Sometimes you will want to controvert polymorphic behavior. If in line 36 we prefix GetPoints with MyClass , then because the MyClass keyword resides in the Polygon class, we would always draw the curve described by the GetPoints method in the Polygon class. That is, changing GetPoints in line 36 to MyClass.GetPoints would mean that we would always display the curve described by the text "Parent" regardless of the type of the object invoking the Draw method.

Most of the time you will want the default polymorphic behavior described earlier. Occasionally you may want to supplant the default behavior and coerce a class to behave in a nonpolymorphic way; however, this is pretty rare.

Replacing Methods and Properties

Sometimes you will want to stifle polymorphic behavior or use a name in a base class a new way in a derived class. This is referred to as shadowing and is accomplished by using the Shadows modifier.

You can add the Shadows modifier to any member of a derived class to hide a member with the same name in a parent class. The member types of the parent and child do not have to be the same. Listing 2.4 demonstrates how to use the Shadows modifier and provides an example of an instance when you might employ Shadows .

Listing 2.4 Using the Shadows Keyword to Reintroduce a Name in a Parent Class
 1:  Public Class Form1 2:      Inherits System.Windows.Forms.Form 3: 4:  [ Windows Form Designer generated code ] 5: 6:    Private Sub Button1_Click(ByVal sender As System.Object, _ 7:      ByVal e As System.EventArgs) Handles Button1.Click 8:      Dim N As New NonShadowed() 9:      N.Number = Convert.ToInt32(TextBox1.Text) 10:   End Sub 11: 12:   Private Sub Button2_Click(ByVal sender As System.Object, _ 13:     ByVal e As System.EventArgs) Handles Button2.Click 14:     Dim S As New Shadowed() 15:     S.Number = Convert.ToInt32(TextBox2.Text) 16:   End Sub 17: End Class 18: 19: Public Class NonShadowed 20:   Public Number As Integer 21: End Class 22: 23: Public Class Shadowed 24:   Inherits NonShadowed 25: 26:   Private Sub Check(ByVal Value As Integer) 27:     Const Mask = "{0} is not within the range 1 to 10" 28: 29:     If (Value < 1) Or (Value > 10) Then 30:       Throw (New Exception(String.Format(Mask, Value))) 31:     End If 32:   End Sub 33: 34:   Public Shadows Property Number() As Integer 35:   Get 36:     Return MyBase.Number 37:   End Get 38: 39:   Set(ByVal Value As Integer) 40:     Check(Value) 41:     MyBase.Number = Value 42:   End Set 43:   End Property 44: 45: End Class 

Suppose you have a class that contains a public field. Further suppose you can't or don't want to change the existing class but want to add some sanity checking on the field; perhaps you want to constrain the possible values of the field. Listing 2.4 contrives such a scenario.

Listing 2.4 contains a form with two TextBox and two Button controls. The first TextBox and Button pair assigns an integer value to a public field Number in the NonShadowed class. The second TextBox and Button pair demonstrates a new class that shadows Number and reintroduces Number as a property. By making Number a property we can add range checking.

Lines 1 through 17 define the Form class with code generated by the Form Designer represented on line 4. Lines 19 through 21 represent a class with a public Integer field, and lines 23 through 45 represent a class derived from the NonShadowed class that shadows the Number member. Line 34 demonstrates the Shadows modifier and the new property introduces range checking. If the consumer assigns a number that is not between 1 and 10 (inclusive), an exception is thrown (lines 26 through 32).

Overloading the Sub New Constructor

Sub New is a Visual Basic .NET constructor. When you introduce additional constructors in the same class, overloading is implicit. When you introduce additional constructors in derived classes, overriding is implicit. Thus you cannot use the Overloads , Overridable , and Overrides modifiers on Sub New .

If you do not define a parameterless constructor for your classes, a default constructor is created for you at runtime. If you need to invoke a constructor on a base class, call MyBase.New , passing to the subclass's constructor any parameters it requires. Default parameterless constructors are invoked on subclasses automatically; thus you only need invoke an inherited constructor if you need to pass arguments to that subclass. Listing 2.2 (page 37) demonstrates an example of calling an inherited, parameter-ized constructor.

Access Modifiers

VB .NET uses an explicit grammar to describe relationships that can be inferred. For example, the Overridable and Overrides modifiers explicitly confer a virtual relationship. This is grammatical handicapping. Because it was anticipated that concepts like virtual methods might be confusing to VB developers, an explicit syntax was devised for VB .NET. Other languages like C++ and C# do not use an explicit syntax; instead, they rely on inference.

Thus far we have not fully discussed the most important modifiers, the ones that support information hiding. Information hiding is the notion of conceptually hiding information to alleviate the burden of class consumers by reducing the number of members a class consumer must become familiar with.

TIP

By keeping the number of public members of a class to just a few, your classes will be easier to use.

NOTE

Information hiding supports ease of use by allowing you to expose only the methods, properties, and events essential for using your classes. The idea that classes will expose only a few key behaviors suggests that you will have a larger number of classes than you would if your classes exposed dozens of public members. This is correct and for good reason.

Singular, short methods are easier to reuse. Classes with a few key public members are easier to use. And since classes are the entities we use to orchestrate new behaviors, it is better to have singular methods and classes with a small number of public members, even if that requires a greater number of classes.

Systems that have more complex members and few classes will likely be more fragile and less extensible than systems with singular methods and a greater number of classes with few public members. The physical analogy that supports this idea is the notion of a band versus an orchestra: It is easier to orchestrate very complex music with a large number of specialized instruments and musicians than it is with a few talented musicians and their instruments.

When you are producing a class, you must concern yourself with every aspect of the class. When you are consuming a class, you need concern yourself only with the public members and the protected members if you are deriving a new class. This is information hiding.

As mentioned in Chapter 1, five modifiers support information hiding. The following bulleted list briefly describes these modifiers.

  • Public denotes members that are accessible by both consumers and producers , that is, everybody.

  • Protected denotes members that are accessible by the producer and by consumers who are generalizing. Generalizing occurs when you are deriving or inheriting from an existing class. (You are likely to encounter the word generalize when you are talking about inheritance in general or when referring to Unified Modeling Language [UML] models.)

  • Friend denotes members that are accessible within the same assembly.

  • Protected Friend denotes a combination of the Friend and Protected modifiers.

  • Private denotes members that are accessible to the producer only. Private members may be accessed only internally in the class in which they are defined.

The question becomes a subjective one. How do you determine which modifier to use? Of course, you could make everything public, but you would be missing out on one of the key tenets of object-oriented programming, information hiding. You could make everything private, but then no one would be able to use your classes. Clearly the answer lies somewhere in between.

All that anyone can tell you are some basic guidelines. This is one of the things that makes programming ( especially object-oriented programming) tough. Object-oriented programming requires a lot of subjective good taste, and taste is, well, a matter of taste.

Below I share with you a few basic rules I apply.

  • Keep public interfaces small, preferably to about a half- dozen public members.

  • Favor many classes with simple interfaces to classes with monolithic, do-all methods.

  • Use as many private methods as you want to support the public behavior; having many singular methods are significantly preferable to having a few monolithic methods. (This latter statement is critical, no matter what anyone else tells you.)

  • If something feels wrong after you have learned more about the system's needs, change it.

  • Use the Protected and Overridable modifiers if you think a consumer may want to extend the behavior. (I have read a few recommendations that suggest you should make all methods protected and virtual because you never know what generalizers will want to reuse, but this is probably overkill and will make your program's internal representation bigger and slower.)

Methods are the things we use to create new behaviors. The more methods we have, the more ways we can orchestrate those methods. Classes are the entities we use to describe a system. The more classes we have, the more flexibility we have in describing the system. If you are going to make a mistake, err on the side of a few short public methods with many protected and private methods and many classes. Monolithic methods and monolithic procedures are often signs of a system that will not be robust or extensible.



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net