嗔秤戻幣�哉膵�云利匈嬉蝕湊蛸賜�塋床四衲���萩晦編報炎嘔囚^泡仟 ̄云利匈��
VB2008貫秘壇欺娼宥(PDF鯉塀哂猟井)-及21何蛍
酔楯荷恬: 梓囚徒貧圭�鮗� ○ 賜 ★ 辛酔堀貧和鍬匈 梓囚徒貧議 Enter 囚辛指欺云慕朕村匈 梓囚徒貧圭�鮗� ● 辛指欺云匈競何! 泌惚云慕短嗤堋響頼���誅卒亮茂�俊彭堋響��辛聞喘貧圭 "辺茄欺厘議箝誓匂" 孔嬬 才 "紗秘慕禰" 孔嬬��
method�察�implying that DepthFirstSearch must be instantiated before we can call FindRoute�┌�。
We should modify the test again as follows�此�
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch = New DepthFirstSearch�┌�
cls。FindRoute�─�Montreal;�察 �Seattle;��
End Sub
To execute the method FindRoute�┌��察�we need to create a DepthFirstSearch object�察�allowing
multiple users to perform searches without getting state mixed up。 At this point�察�we could pat
ourselves on the back and think that we have written a good test that requires a class
implementation。
The Problem of Magic Data
Our test is not yet plete�察�because we don¨t have access to the route found by the algorithm�察 �
but that will be explained in a moment。
In the implementation of DepthFirstSearch�察�a reference to the data structure is necessary。
The search algorithm needs to know which tree to navigate。 One way to implement a reference
to the tree is to directly reference the shared data Node。RootNodes。 An implementation of
DepthFirstSearch would be as follows�此�
´´´´´´´´´´´´´´´´´´´´´´Page 122´´´´´´´´´´´´´´´´´´´´´´´
100 CH AP T E R 4 * L E A R N IN G AB OU T D AT A S TR U CT U R E S�察 �DE CI SI ON S�察 �A N D L O OP S
Public Class DepthFirstSearch
Public Sub FindRoute��ByVal start As String�察�ByVal finish As String��
Dim startNodes As Node�┌� = Node。RootNodes
End Sub
End Class
This example declares a variable called startNodes�察�which represents the starting point
and root of the tree as shown in Figure 4´2。 The root of the tree is based on the data member
Node。RootNodes�察�and this assignment is called a magic type assignment。 A magic type is formed
when you call a method�察�and magically�察�it happens to know how to reference data�察�even though
you never instructed the type。 In the case of DepthFirstSearch�察�the magic is the ability of
FindRoute�┌� to know to reference the correct data member RootNodes。
The assumption is bad because it couples the data member RootNodes to the method
FindRoute�┌�。 Imagine if the developer of the Node class later decides to add functionality to load
the tree from a file on the hard disk。 So that FindRoute�┌� is not broken�察�the developer would
need to explicitly copy the hard´disk´loaded tree to the data member RootNodes。
Or what if two different users wanted to create two different flight trees�拭�Node。RootNodes is
a shared resource�察�and thus can process only a single flight tree。 The developer of Node might
alter RootNodes�察�and thus FindRoute�┌� would behave erratically。
When you have a case of magic data�察�whatever data is magic needs to be passed to the type
via a constructor or other method。 So the test for the flight route would change to the following�此�
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch= _
New DepthFirstSearch��Node。RootNodes��
cls。FindRoute�─�Montreal;�察 �Seattle;��
End Sub
As the root tree node is required�察�we change the constructor to require that a caller pass in the
root tree node。 The test code still uses the shared data member RootNodes�察�but DepthFirstSearch
does not need to know where to find the tree。 If the Node developer were to alter the behavior of
the data member RootNodes�察�then only the constructor code to DepthFirstSearch would need
altering�察�not the FindRoute�┌� method。 Thus�察�Node and DepthFirstSearch are properly decoupled
from each other。
Getting the Found Route
Once you have called the FindRoute�┌� method�察�you expect an answer。 Because the route could
involve multiple cities�察�the found route is stored in an array of Node elements。 In programmatic
terms�察�there are two ways of retrieving the array of Nodes。 The first is a return value�察�like this�此�
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch = New DepthFirstSearch��Node。RootNodes��
Dim foundRoute As Node�┌� = cls。FindRoute�─�Montreal;�察 �Seattle;��
End Sub
The bold code shows the assignment of the return value to the variable foundRoute。
´´´´´´´´´´´´´´´´´´´´´´Page 123´´´´´´´´´´´´´´´´´´´´´´´
CH AP T E R 4 * L E A R N I N G A B OU T D AT A S TR U CT U R E S�察 �DE CI SI ON S�察 �A N D L O OP S 101
The second approach is for the FindRoute�┌� method to store the result in an internal data
member。 The following example assumes that the FindRoute�┌� method stores the result in a
data member named FoundRoute�此�
Public Sub TestSearch�┌�
Dim cls As DepthfirstSearch = New DepthFirstSearch��Node。RootNodes��
cls。FindRoute�─�Montreal;�察 �Seattle;��
Dim foundRoute As Node�┌� = cls。FoundRoute
End Sub
Each approach seems acceptable�察�and you are not sure which to use。 When you have a
choice like this�察�you need to make a decision。 The safest way to make a decision is to write tests
and see if there are any problems with either approach。
In the example of calculating a single route�察�either approach is fine。 But let¨s look at the
code when multiple routes are being searched。 First�察�consider the code where the found path
is a return parameter value�此�
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch = New DepthFirstSearch��Node。RootNodes��
Dim foundRoute1 As Node�┌� = cls。FindRoute�─�Montreal;�察 �Seattle;��
Dim foundRoute2 As Node�┌� = cls。FindRoute�─�New York;�察 �Seattle;��
End Sub
Now take a look at the code that uses the data member�此�
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch = New DepthFirstSearch��Node。RootNodes��
cls。FindRoute�─�Montreal;�察 �Seattle;��
Dim foundRoute1 As Node�┌� = cls。FoundRoute
cls。FindRoute�─�New York;�察 �Seattle;��
Dim foundRoute2 As Node�┌� = cls。FoundRoute
End Sub
Again�察�it would seem that both choices are adequate。 However�察�there is a difference�察�the
difference is subtle�察�but distinct enough to matter。 In the test implementation where the found
route is a return value�察�the variables foundRoute1 and foundRoute2 represent routes that relate
directly to the route being searched。 There is no chance that the variables foundRoute1 can
represent the route New York�CSeattle。 With the data member code�察�it could happen that
foundRoute1 points to the route New York�CSeattle�察�as shown in the following code。
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch = New DepthFirstSearch��Node。RootNodes��
cls。FindRoute�─�Montreal;�察 �Seattle;��
cls。FindRoute�─�New York;�察 �Seattle;��
Dim foundRoute1 As Node�┌� = cls。FoundRoute
Dim foundRoute2 As Node�┌� = cls。FoundRoute
End Sub
´´´´´´´´´´´´´´´´´´´´´´Page 124´´´´´´´´´´´´´´´´´´´´´´´
102 CH AP T E R 4 * L E A R N IN G AB OU T D AT A S TR U CT U R E S�察 �DE CI SI ON S�察 �A N D L O OP S
By switching the order of the FindRoute�┌� method calls and references to the data member
FoundRoute�察�the variables foundRoute1 and foundRoute2 will reference the same found route�察 �
specifically the route New York�CSeattle。 This is not a good idea。 The example shows how data
members have no direct relation to methods and can vary independently。
So the choice of returning the found route from a method is the better and more robust
approach。
*Note Data members are useful when you want to store or retrieve data that spans multiple method calls
or is not dependent on the order of how methods are called。 When you have data that is dependent on the
order of called methods�察�you should use the Return keyword or ByRef parameters。
The following is the plete test case that includes the verification code that searches for
a flight from Montreal to Seattle。
Public Sub TestSearch�┌�
Dim cls As DepthFirstSearch = New DepthFirstSearch��Node。RootNodes��
Dim foundRoute As Node�┌� = cls。FindRoute�─�Montreal;�察 �Seattle;��
If foundRoute。Length 2 Then
Console。WriteLine�─�Incorrect route as route has two legs;��
End If
If foundRoute��0��。CityName。pareTo�─�Los Angeles;�� 0 Then
Console。WriteLine�─�Incorrect as first leg is Los Angeles;��
End If
End Sub
*Note We¨ve already used the If construct in earlier chapters。 It tests a condition and executes its contained
code if that condition is true。 The means does not equal。 We¨ll examine If in more detail later in this
chapter�察�in the ^Using the If Statement ̄ section。
Implementing the Depth´First Search Algorithm
The implementation of the depth´first search algorithm involves creating an algorithm that
iterates the tree。 Here�察�we¨ll implement the algorithm in Visual Basic。 In so doing�察�we¨ll use
decision statements and For loops to iterate the array data。 These are incredibly mon in
Visual Basic programs�察�and life would be very difficult without them。
We implemented the test code in the previous section�察�so the next step is to implement a
version of DepthFirstSearch that represents a shell�察�so that all of the code piles and runs。
The shell is structural and is used to hold up the entire application。 It is defined as shown in
Figure 4´15。
´´´´´´´´´´´´´´´´´´´´´´Page 125´´´´´´´´´´´´´´´´´´´´´´´
CH AP T E R 4 * L E A R N I N G A B OU T D AT A S TR U CT U R E S�察 �DE CI SI ON S�察 �A N D L O OP S 103
Tree to be searched is passed as a
parameter to the constructor
Public Class DepthFirstSearch
Private root As Node�┌�
Constructor parameter is assigned to a private data
Public Sub New��ByVal root As Node�┌���
Me。root = root member。 Making a data member private means only those
End Sub methods declared in DepthFirstSearch can reference it。
Public Function FindRoute��ByVal start As String�察�ByVal finish As String�� _
As Node�┌�
Throw New Exception�─�Not implemented;��
End Function
End Class
Empty implementation throws an
exception ��an error�� indicating that the
code is not implemented
Figure 4´15。 The initial shell of the depth´first algorithm
With a shell implemented�察�you could run the application and see if everything works。 If
you do run the test code�察�you will get an error�察�because calling FindRoute�┌� generates an excep
tion that indicates FindRoute�┌� has not been fully implemented。 ��Exceptions are discussed in
detail in the next chapter。�� However�察�the shell is plete�察�and we are ready to implement the
guts of the algorithm。
Implementing the guts of an algorithm is arguably one of the most difficult steps�察�as you
must go through the logic of what you want to do。 Whenever I am confronted with an algorithm
that needs implementation and I am not quite sure how to proceed�察�I just write code�察�based on
an entry point ��the method call�� and an exit point ��the end of a method¨s execution��。
The Keyhole Problem
In the example�察�the entry and exit point into the algorithm is FindRoute�┌�。 In turn�察�the entry of
FindRoute�┌� is two parameters�此�start�察�indicating the beginning city�察�and finish�察�indicating the
destination city。 The exit of FindRoute�┌� is an array of Node objects。
The array of Node objects needs to be preallocated with space so that all of the found cities
can be added。 We can make an assumption at this point that we preallocate the number of
nodes to the length of the data member DepthFirstSearch。root plus one。 The assumption is
that the longest trip cannot exceed the number of cities available。 We know that the root node
is an array of all starting point cities�察�thus the allocation can never be exceeded。
Focusing on the FindRoute�┌� method�察�the updated code looks like this�此�
Public Function FindRoute��ByVal start As String�察�ByVal finish As String�� As Node�┌�
Dim returnArray��Me。root。Length �� 1�� As Node
Return returnArray
End Function
The code with the array allocation is a classic keyhole problem ��an idea first introduced by
Scott Meyers�察�see http��//aristeia。/TKP/��。 The problem of a keyhole is that you imple
ment an algorithm based on assumptions that cause you to write code that works for that specific
context�察�but would fail when executed in another context。
´´´´´´´´´´´´´´´´´´´´´´Page 126´´´´´´´´´´´´´´´´´´´´´´´
104 CH AP T E R 4 * L E A R N IN G AB OU T D AT A S TR U CT U R E S�察 �DE CI SI ON S�察 �A N D L O OP S
The code allocates an array to the length of the root tree structure�察�and that is making a
grand assumption。 Imagine if the Node developers decided to introduce connections that could
be reached only via another city that is not included in the root nodes。 At that point�察�you could
potentially exceed the available space in the array。 Another solution would be to allocate an
array of arbitrary length X 。 But then�察�if there were X��1 unique cities�察�another array could be
violated。
The simplest solution would be to not allocate an array�察�but instead figure out how many
elements you needed after having found a path。 However�察�this would not work�察�because then
you would have no idea which city you had already visited。 Another solution ��which will be
discussed in Chapter 9�� would be to use a collection class。
In this case�察�we are going to wash our hands of the problem and force the Node developers
to modify their class。 The Node developers are going to add a shared method that tells the search
algorithm how big the array needs to be。 The following is the modified FindRoute�┌� code。
Public Function FindRoute��ByVal start As String�察�ByVal finish As String�� As Node�┌�
Dim returnA
酔楯荷恬: 梓囚徒貧圭�鮗� ○ 賜 ★ 辛酔堀貧和鍬匈 梓囚徒貧議 Enter 囚辛指欺云慕朕村匈 梓囚徒貧圭�鮗� ● 辛指欺云匈競何!
梁椣戻幣�� 梁心弌傍議揖扮窟燕得胎��傍竃徭失議心隈才凪万弌誌育断蛍�輌臆惨軼僑〃�燕慕得珊辛參資誼持蛍才将刮襲潜��範寔亟圻幹慕得 瓜寡追葎娼得辛參資誼寄楚署衛、持蛍才将刮襲潜填��