Defining Nested Classes

Team-Fly    

 
Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 7.  Creating Classes

Defining Nested Classes

A nested class is quite simply a class defined within a class. Nested classes can be defined with all of the elements of any other class. Here's the skeletal code for a nested class:

 Public Class Outer   Public Class Nested   End Class End Class 

Your motivation for creating nested classes is to collect a group of elements that make sense together and only within the containing class. For example, if you have a class and some fields, properties, and methods that define a concept separate but a part of the containing class and you want to store multiple variations of state, you might implement a nested class.

In general you won't need to implement nested classes. Any implementation that can be done with nested classes can be done without nested classes. If you are interested, you can look into the advanced idioms, including the letter-envelope idiom, discussed by James Coplien in Advanced C++ . Listing 7.7 provides an example of nested classes, demonstrating the grammatical technique.

Listing 7.7 Multiple nested classes, inheritance, and multithreading in the TrafficLight.sln demo program
  1:  Public Class Signal  2:  #Region " Public Members "  3:  Public Event OnChange(ByVal sender As System.Object, _  4:  ByVal e As System.EventArgs)  5:   6:  Public Sub Draw(ByVal Graphic As Graphics)  7:  Graphic.FillRectangle(Brushes.Brown, Rect)  8:  DrawLights(Graphic)  9:  End Sub  10:   11:  Public Sub New(ByVal Rect As Rectangle)  12:  FRect = Rect  13:  PositionLights()  14:  TurnOnGreen()  15:  CreateThread()  16:  End Sub  17:   18:  Public Sub Dispose()  19:  Static Done As Boolean = False  20:  If (Done) Then Exit Sub  21:  Done = True  22:  KillThread()  23:  GC.SuppressFinalize(Me)  24:  End Sub  25:   26:  Public Sub NextSignal()  27:  While (FThread.IsAlive)  28:  FThread.Sleep(SleepTime())  29:  ChangeState()  30:  DoChange()  31:  End While  32:  End Sub  33:   34:  #End Region  35:   36:  #Region " Protected Members "  37:   38:  ' Destructor  39:  Protected Overrides Sub Finalize()  40:  Dispose()  41:  End Sub  42:   43:  Protected Sub CreateThread()  44:  FThread = New Threading.Thread(AddressOf NextSignal)  45:  FThread.IsBackground = True  46:  StartSignal()  47:  End Sub  48:   49:  Protected Sub StartSignal()  50:  FThread.Start()  51:  End Sub  52:   53:  Protected Sub StopSignal()  54:  FThread.Abort()  55:  FThread.Join()  56:  End Sub  57:   58:  Protected Sub KillThread()  59:  StopSignal()  60:  FThread = Nothing  61:  End Sub  62:   63:  Protected Sub DrawLights(ByVal Graphic As Graphics)  64:  Dim I As Integer  65:  For I = 0 To FLights.GetUpperBound(0)  66:  FLights(I).Draw(Graphic)  67:  Next  68:  End Sub  69:   70:  #End Region  71:   72:  #Region " Private Members "  73:  Private FLights() As Light = _  74:  {New GreenLight(), New YellowLight(), New RedLight()}  75:  Private FRect As Rectangle  76:  Private FThread As Threading.Thread  77:   78:  Private ReadOnly Property Green() As Light  79:  Get  80:  Return FLights(0)  81:  End Get  82:  End Property  83:   84:  Private ReadOnly Property Yellow() As Light  85:  Get  86:  Return FLights(1)  87:  End Get  88:  End Property  89:   90:  Private ReadOnly Property Red() As Light  91:  Get  92:  Return FLights(2)  93:  End Get  94:  End Property  95:   96:  Private ReadOnly Property Rect() As Rectangle  97:  Get  98:  Return FRect  99:  End Get  100:  End Property  101:   102:  Private Sub PositionLights()  103:   104:  Dim I As Integer  105:  For I = 0 To FLights.GetUpperBound(0)  106:  FLights(I).Rect = GetRect(I)  107:  Next  108:   109:  End Sub  110:   111:  Private Sub TurnOnGreen()  112:  Green.State = True  113:  End Sub  114:   115:  Private Sub DoChange()  116:  RaiseEvent OnChange(Me, Nothing)  117:  End Sub  118:   119:   120:  Private Function GetRect(ByVal Index As Integer) As Rectangle  121:  Return New Rectangle(Rect.Left + 10, _  122:  Rect.Top + 10 + CInt(Math.Round((2 - Index) / 3 * Rect.Height)), _  123:  Rect.Width - 20, CInt(Math.Round(Rect.Height / 3)) - 20)  124:  End Function  125:   126:  Private Sub ChangeState()  127:  Static Current As Integer = 0  128:  Current = (Current + 1) Mod 3  129:  Dim I As Integer  130:   131:  For I = FLights.GetLowerBound(0) To _  132:  FLights.GetUpperBound(0)  133:  FLights(I).State = I = Current  134:  Next  135:  End Sub  136:   137:  Private Function SleepTime() As Integer  138:  Static T() As Integer = {1000, 5000}  139:  If (Yellow.State) Then  140:  Return T(0)  141:  Else  142:  Return T(1)  143:  End If  144:  End Function  145:   146:  #End Region  147:   148:   149:  #Region " Nested Members "  150:   151:  ' Virtual Abstract Nested Light Class  152:  ' Base class for the signal light classes  153:   154:  Private MustInherit Class Light  155:  Public State As Boolean = False  156:  Public Rect As Rectangle  157:   158:  Protected MustOverride ReadOnly Property _  159:  BrushArray() As Brush()  160:   161:  Protected Function GetBrush() As Brush  162:  If (State) Then  163:  Return BrushArray(1)  164:  Else  165:  Return BrushArray(0)  166:  End If  167:  End Function  168:   169:  Public Sub Draw(ByVal Graphic As Graphics)  170:  Graphic.FillEllipse(GetBrush(), Rect)  171:  End Sub  172:   173:  Protected Overrides Sub Finalize()  174:  Rect = Nothing  175:  End Sub  176:  End Class  177:   178:  ' RedLight nested class, inherits from Light  179:  Private Class RedLight  180:  Inherits Light  181:   182:  Protected Overrides ReadOnly Property BrushArray() As Brush()  183:  Get  184:  Static Brush() As Brush = {Brushes.Gray, _  185:  Brushes.Red}  186:  Return Brush  187:  End Get  188:  End Property  189:  End Class  190:   191:  ' YellowLight nested class, inherits from Light  192:  Private Class YellowLight  193:  Inherits Light  194:   195:  Protected Overrides ReadOnly Property BrushArray() As Brush()  196:  Get  197:  Static Brush() As Brush = {Brushes.Gray, _  198:  Brushes.Yellow}  199:  Return Brush  200:  End Get  201:  End Property  202:   203:  End Class  204:   205:  ' GreenLight nested class, inherits from Light  206:  Private Class GreenLight  207:  Inherits Light  208:  Protected Overrides ReadOnly Property BrushArray() As Brush()  209:  Get  210:  Static Brush() As Brush = {Brushes.Gray, _  211:  Brushes.Green}  212:  Return Brush  213:  End Get  214:  End Property  215:  End Class  216:  #End Region  217:   218:  End Class 

Because the listing is very long, the elaboration is subdivided into individual subsections. Each of the following subsections elaborates on a specific aspect of Listing 7.7.

Understanding the Motivation for Nested Classes

The motivation for using nested classes at all is questionable, because any class that can be nested could technically be implemented as a non-nested class. There might be some strategic reasons for implementing nested classes, but you will find them few and far between.

To demonstrate the technical aspects of implementing nested classes, the TrafficSignal.sln sample program was contrived. As a suggestion, the traffic signal lightsred on top, yellow in the middle, and green on the bottommight make sense as nested classes. You are unlikely to encounter a need for a single-signal light that maintains state relative to other lights without the other lights. (You could have a single blinking yellow light, but you could implement that as an element of a different traffic signal. However, this does illustrate what you are likely to encounter when using nested classes: Each time you implement a nested class, you are likely to find an exception that suggests the class might be better off not nested.)

The Signal contains three instances of a Light class. Because the Lights only make sense in the context of the entire signalor so we assume for our purposesand we need more than one light, we implement the light as a nested class and create three instances of the light.

Defining the Signal Class

The Signal class represents a three-state , red-yellow-green traffic light. The signal will be defined to maintain its state and draw itself on a device context, in a Visual Basic .NET Graphics object.

The Signal class will have an array of three Light objects and a means of changing state in an ordered way from Green to Yellow to Red. Two signals at different signal-states are shown in Figure 7.4.

Figure 7.4. Two instances of the Signal class from TrafficSignal. sln.

graphics/07fig04.jpg

All of the members of the Signal class are defined on lines 1 through 146 of Listing 7.7. The subsections that follow elaborate on the different aspects of the Signal class.

Using Code Outlining to Organize

The first thing you will notice about Listing 7.7 is that it employs code outlining using the #Region pragma. A long, complicated class listing like the Signal class will benefit from a little extra housekeeping. The top half of the class contains the members of the Signal class, and the bottom half, starting on line 149, contains the nested classes. By adding #Region pragmas, you can collapse and expand whole subsections of your code to make development easier.

A reasonable practice to employ is to list members by priority. Consumers will only be interested in Public members, so Public members are listed first. Generalizers will be interested in Protected members too, so those are listed after the Public members.

Finally, Private members are only of interest to a producerthe person who implements the class; thus Private members are listed last.

Tip

A little housekeeping helps, but if the class is relatively simple, the members are added as they evolve , in no particular order.


By their sheer existence, nested members are the least significant members of a class. If the nested classes were significant to consumers, they wouldn't be nested; consequently, nested classes are listed last.

Defining the Constructor and Destructor

The Signal class has three instances of the Light class and a Thread object. For this reason, a constructor, destructor, and Dispose methods were added to the Signal class. The constructor and the Dispose method are defined on lines 11 to 16 and 18 to 24, respectively. The excerpt from Listing 7.7 is shown in Listing 7.8 for convenience. The Protected Finalize method simply calls the Dispose methodlines 39 to 41and isn't repeated here.

Listing 7.8 The constructor and Dispose methods of the Signal class
  11:  Public Sub New(ByVal Rect As Rectangle)  12:  FRect = Rect  13:  PositionLights()  14:  TurnOnGreen()  15:  CreateThread()  16:  End Sub  17:   18:  Public Sub Dispose()  19:  Static Done As Boolean = False  20:  If (Done) Then Exit Sub  21:  Done = True  22:  KillThread()  23:  GC.SuppressFinalize(Me)  24:  End Sub 

The constructor Sub New delegates all of its tasks to well-named methods. The names of the methods tell you what is happening. Another strategy is to add an Initialize method and delegate all initialization code to that method; this technique allows you to reinitialize an object without constructing a new object. The constructor clearly indicates that the bounding Rectangle is being cached to a Field, the Lights are positioned, the green light is turned on, and the Thread is created. (Perhaps RunThread would more clearly indicate that the thread is created and running after line 15 runs.)

The Dispose method uses a static local variable to act as a sentinel. After Dispose is called one time, this method becomes a noop method. All Dispose does is kill the thread that controls the light states and suppress garbage collection. The lights themselves aren't released because they were created outside of the constructor on lines 73 and 74 with an array initializer.

Creating the Signal Thread

Line 15 (see Listing 7.7 or 7.8) calls the CreateThread method. All of the thread-management methods are shown in Listing 7.9, which is an excerpt from Listing 7.7.

Listing 7.9 The thread-management methods of the Signal class
  43:  Protected Sub CreateThread()  44:  FThread = New Threading.Thread(AddressOf NextSignal)  45:  FThread.IsBackground = True  46:  StartSignal()  47:  End Sub  48:   49:  Protected Sub StartSignal()  50:  FThread.Start()  51:  End Sub  52:   53:  Protected Sub StopSignal()  54:  FThread.Abort()  55:  FThread.Join()  56:  End Sub  57:   58:  Protected Sub KillThread()  59:  StopSignal()  60:  FThread = Nothing  61:  End Sub 

Line 44 creates an instance of the System.Threading.Thread class. The Thread class constructor takes the AddressOf of a procedure; this procedure is where the thread will fork when the thread is started. Line 45 indicates that the thread is a background thread, allowing the application to quit and stop the signal threads. Line 46 calls StartSignal, which turns the traffic signal on by starting the subroutine on line 49. When Dispose is called, Dispose calls KillThread. From the listing you can determine that KillThread calls StopSignal and releases the thread object by assigning it to Nothing.

StopSignal calls the FThread.Abort method and FThread.Join waits for the thread to actually stop executing. FThread.Abort kills the thread by raising an uncatchable ThreadAbortException; the exception allows all of the Finally blocks in the thread to execute, so you need to call Join to ensure that the thread has completed.

Multithreading is brand new in Visual Basic .NET. If you haven't programmed in a multithreaded language before, threads might be a challenge to you. The Thread class and multithreaded programming in Visual Basic .NET are covered in detail in Chapter 14, "Multithreaded Applications."

Drawing the Signal Using an Event

Signal objects are drawn on whatever control passes its Graphics object to the Signal.Draw method. (In reality the signal should be a control, but we would have to digress too far from our discussion of nested classes to get there from here.) Consider these code excerpts:

  6:  Public Sub Draw(ByVal Graphic As Graphics)  7:  Graphic.FillRectangle(Brushes.BurlyWood, Rect)  8:  DrawLights(Graphic)  9:  End Sub ...  63:  Protected Sub DrawLights(ByVal Graphic As Graphics)  64:  Dim I As Integer  65:  For I = 0 To FLights.GetUpperBound(0)  66:  FLights(I).Draw(Graphic)  67:  Next  68:  End Sub ...  161:  Protected Function GetBrush() As Brush  162:  If (State) Then  163:  Return BrushArray(1)  164:  Else  165:  Return BrushArray(0)  166:  End If  167:  End Function  168:   169:  Public Sub Draw(ByVal Graphic As Graphics)  170:  Graphic.FillEllipse(GetBrush(), Rect)  171:  End Sub 

Drawing is performed in three methods. Signal.Draw on lines 6 to 9 simply draws the Signal as a Rectangle, and calls Signal.DrawLights to draw each of the contained lights. DrawLights iterates over each light and calls the light's draw methods, demonstrating the division of labor offered by classes and nested classes in this instance.

Light.Draw simply calls Graphics.FillEllipses (using the argument Graphic), using each object's cached Rect as the bounding region of the light and the Light.GetBrush method to determine the brush based on the state of the light. When the drawing method is invoked it only cares about Ellipses, Brushes, and Rects, not states and calculating bounding regions .

Line 163 and 165 (see Listing 7.7 or 7.9) uses the polymorphic property BrushArray to return the correct array of brush objects based on the instance of the lamp. If State = False, the off brush is returned; if State = True, the on brush is returned.

Chapter 10 offers an extended discussion on inheritance relationships and how polymorphic behavior like the BrushArray() property works, as well as a discussion on defining and implementing interfaces.

Defining the Abstract Base Class, Light

Light, GreenLight, YellowLight, and RedLight are all nested classes. Light uses the MustInherit class modifier. The Light class demonstrates abstract base classes in Visual Basic .NET, which means that Light is intended to be descended from but never directly created. Light has to use MustInherit because it declares an abstract property:

 Protected MustOverride ReadOnly Property BrushArray() As Brush() 

Notice that BrushArray from lines 158 and 159 of Listing 7.7 has no procedure block; it's a declaration only. Statements with MustOverride refer to virtual and abstract members, which means they must be implemented in a descendant class.

Virtual, abstract members are analogous to interface declarations in COM classes. GreenLight, YellowLight, and RedLight must each implement the property BrushArray or they will be virtual and abstract.

Declarative statements are used to guide the implementation of descendant classes. The way Light is implemented, its Draw method depends on GetBrush, and GetBrush, in turn , depends on being able to get the correct brush and color based on the state of a specific light. In structured languages, or incorrectly in object-oriented languages, this kind of behavior was implemented using a case statement.

Implementing the Signal Lights

The signal light classes themselves are easy to implement. Each specific light need only inherit from Light and implement the read-only property BrushArray. Listing 7.10 demonstrates the RedLight class only. (All of the lights are identical except for the values used to initialize the BrushArray.)

Listing 7.10 Implementation of one of the signal lights
  178:  ' RedLight nested class, inherits from Light  179:  Private Class RedLight  180:  Inherits Light  181:   182:  Protected Overrides ReadOnly Property BrushArray() As Brush()  183:  Get  184:  Static Brush() As Brush = {Brushes.Gray, _  185:  Brushes.Red}  186:  Return Brush  187:  End Get  188:  End Property  189:  End Class 

Line 182 implements a protected, overridden, read-only property named BrushArray. A Static array of brushes is initialized to global brushes defined in the Brushes object. The RedLight class uses gray- and red-colored brushes.

There are several ways we could have implemented the colored signal lights. We could have used one Light class and a factory method that initializes the brush values based on the kind of light; kind could have been implemented using an enumeration. In software, as in all things, you eventually have to pick a direction and run with it. I specifically picked this implementation strategy to demonstrate nested classes, but I do like the way it turned out.

A Final Word on the Demo Program

In a production system, the traffic signal sample program would better serve an application as a component. If you were modeling a city's traffic lights, you would want to rethink the threads, too. In a city the size of Okemos, you might have 30 threads, but in Chicago you would blow up the computer switching all of the necessary threads. Additionally, the signal lights would need to be coordinated. A better implementation might be one threaded manager that coordinates lights at opposing intersections and manages the timing from block to block.

Finally, use better graphics if you are going to simulate the visual aspects of traffic management, or anything else for that matter.


Team-Fly    
Top
 


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 222

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