Tải bản đầy đủ (.pdf) (16 trang)

Tài liệu Write Data Validation Code That Can Be Reused in Other Classes docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (50.52 KB, 16 trang )

9.7 Write Data Validation Code That Can Be Reused in Other Classes
As you were writing the PhoneNumber and CustomerID validation code in the previous
section, you might have thought that this code would be extraordinarily useful in other
parts of the Northwind application. For example, the Suppliers and Employees tables also
have Phone and Fax fields, and the CustomerID column is also defined in the Orders
table.
Technique
In this section, you will pull the data validation code you wrote for both the
PhoneNumber and CustomerID columns and create independent objects that encapsulate
your existing validation logic. Then you will update the CCustomer class to make use of
these new classes.
The CCustomerID class will be a simple class that performs two functions: checking the
length of the ID and looking in the database to see if the CustomerID exists.
For the PhoneNumber data validation, you will learn how to create an entire object model
that will provide you with data validation for different types of phone numbers with a
bare minimum of code. And, just for the fun of it, you will learn how to use the same
base class you use to validate phone numbers to validate Social Security numbers.
Steps
1. Add a new class file to your project by right-clicking on the project in the Solution
Explorer window and selecting Add Class from the Add submenu. Name the new
class CCustomerID.vb.
2. Copy the DoesCustomerIDExist and ValidateCustomerID methods from the
CCustomer class into the CCustomerID class. Rename them Exists and Validate,
respectively.
3. Copy the InvalidCustomerIDException from CustomerClass.vb and paste it inside
the CCustomerID class. This makes your exceptions directly related to the
CCustomerID.
4. One gap in splitting off the CustomerID property into its own class is that some
parts of your application might want a read/write CustomerID property, whereas
others, such as the CCustomer class, need a ReadOnly property. Instead of having
a Boolean set to lock the property, a more flexible way is to add an event to your


class that is called before setting the property, allowing a containing class to
cancel the change.
5. Public Event BeforeUpdate(ByVal pstrCustomerID As String, ByRef pfCancel As
Boolean)
6. Now add a new property called CustomerID to the CCustomerID class, as shown
in Listing 9.42. This property should raise the BeforeUpdate event, and if the
changes are not cancelled, call the Validate method and throw an
InvalidCustomerIDException if the CustomerID is invalid.
Listing 9.42 CCustomerID.vb: The CustomerID Property of the
CCustomerID Class
Property CustomerID() As String
Get
Return mstrCustomerID
End Get
Set(ByVal Value As String)

Dim fCanceled As Boolean
RaiseEvent BeforeUpdate(Value, fCanceled)

If Not fCanceled Then

mfValid = Validate(Value)
If mfValid Then
mstrCustomerID = Value
Else
Throw New InvalidCustomerIDException(Value)
End If

End If
End Set

End Property
7. Then add a new constructor that accepts a CustomerID as a parameter. That
constructor, shown in Listing 9.43, should call the property statement, which will
handle validation.
Listing 9.43 CCustomerID.vb: The Constructor for the CCustomerID
ClassPublic Sub New(ByVal pID As String)
Me.CustomerID = pID
End Sub
8. You're almost finished with the CCustomerID class, except for one subtle issue:
pointers. Everything you've written in this chapter so far has used base datatypes,
so you haven't had to worry about copying values between function calls. But
objects work differently than base datatypes do.
The issue is with ByVal and ByRef. With base datatypes, the distinction is fairly
straightforward: ByVal passes a copy of the value of the variable to the function.
The called function could do whatever it pleased to the passed value without
impacting the value of the variable in the calling function. ByRef passes the called
function a pointer instead of the value, so any change made to the variable in the
called function is made to the variable in the calling function.
With objects such as CCustomerID, it's completely different. Whether you use
ByVal or ByRef, you're still working with a pointer. The called function will
always modify the object that the calling function passes.
The difference is in reassigning the pointer. ByVal passes a pointer to an object. If
the called function changes the pointer to a new object, the calling function will
still point at the original object. ByRef passes a pointer to a pointer to an object. If
the called function changes the pointer to a new object, the calling function will
now point at the new object instead of the original object.
The point(er) here is in the constructors. You want to provide a way for other
developers to create copies of the class easily, or you might end up with the same
CCustomerID object being used simultaneously in a potentially conflicting
fashion. The solution? Add a constructor to the CCustomerID class that accepts a

CCustomerID object as a parameter, as shown in Listing 9.44.
Listing 9.44 CCustomerID.vb: An Object-Based Constructor for the
CCustomerID Class
Public Sub New(ByVal pID As CCustomerID)
' Both ByVal and ByRef pass object pointers, so if you
' want to create a new CustomerID instance based on an
' existing instance, you need to copy the values of the
' base datatypes to a new instance.
Me.CustomerID = pID.CustomerID
End Sub
9. Updating the CCustomer class is not difficult, but it does require many small,
similar changes, including the following:
o
Retyping variables
o
Removing code validating CustomerIDs and related exception throwing
o
Calling CCustomerID's object-based constructor from Listing 9.43 each
time a CCustomerID object is passed into a CCustomer object
o
Adding .CustomerID after every CCustomerID variable where the string
value is needed
The constructor in Listing 9.45 that you use to create new customers in the
database is a good example of all of these changes.
Listing 9.45 CCustomerID.vb: An Excerpt of a CCustomer Constructor as
Modified to Use the CCustomerID Class
Public Sub New(ByVal pCustomerID As CCustomerID,
ByVal pCompanyName As String)
' Change A: retyping variables
' Instead of accepting a string that has to be validated,

' this constructor accepts a CCustomerID object that by
' definition is valid.

If pCustomerID.Exists Then
' Change B: removing code validating CustomerIDs
' If you recall, there used to be an additional condition
' in this If... Then that checked the length of the CustomerID.
' Now, the CCustomerID guarantees a valid CustomerID.
mdsCust = New dsCustomers()
mfNew = True

' Change C: using the object-based constructor
' Just to make sure, create a new CCustomerID object. If you
' don't, consumers of this class will retain a pointer to this
' object instance, allowing that consumer to change the value
' of the shared CCustomerID object unbeknownst to the CCustomer object.
mCustomerID = New CCustomerID(pCustomerID)

Me.CompanyName = pCompanyName
Else
' Change D: calling the CustomerID property when you need a string.
' When you throw an InvalidCustomerIDEException, you won't necessarily
' have a valid CustomerID, so this exception has to accept a string
' instead of an object. This InvalidCustomerIDException is used to express
' that the CustomerID for the new customer already exists in the database.
Throw New
CCustomerID.InvalidCustomerIDException(pCustomerID.CustomerID)
End If

End Sub

Tip

One way to identify all these changes is to change the type of the
CustomerID property in the ICustomer interface, as well as the
name of the class-level variable in the CCustomer class that holds
CustomerID values. Because this will invalidate much of the code
utilizing CustomerIDs, Visual Studio .NET will put a wavy blue
line under the code you need to modify.
10. You will also need to redeclare the mCustomerID variable using the WithEvents
keyword and add an event handler that cancels the change, as shown in Listing
9.46.
Listing 9.46 frmHowTo9_7.vb: Adding Event Handlers to CCustomer to
Handle the CCustomerID BeforeUpdate Event
Private WithEvents mCustomerID As CCustomerID

Private Sub mCustomerID_BeforeUpdate(ByVal pstrCustomerID As String,
ByRef fCancel As Boolean) Handles mCustomerID.BeforeUpdate
fCancel = True
End Sub
The next task in this section is to provide similar validation functionality for phone
numbers that can be used throughout your application. At the end of this task, you
will have four separate classes, all of which extend the functionality of a fifth base
class. Before you begin coding so many classes, it is always a good idea to plan
precisely what you want to write.
Figure 9.8 is a class diagram that describes the classes you're going to write and
their relationship to each other. The class at the top of the diagram is our base
class. The base class has only one purpose: It contains a method that checks a
string of numbers to see whether the string has invalid characters. All of the other
classes will inherit this validation method, but each class will change the definition
of valid characters in the string.

Figure 9.8. A class diagram describing the classes to be developed in section
9.7.

According to the class diagram, this class has only seven members: two variables
(cValidChars and mstrValue), one property (StringValue), three methods (IsValid,
ThrowException, and DefineValidChars), and one member class
(InvalidNumberStringException).
You might have noticed a symbol before each member declaration. This symbol
refers to the accessibility of the member. A minus sign (-) means the member is
Private, a number sign (#) means Protected, and a plus sign (+) means Public. If
this doesn't make sense at the moment, don't worry. It will be much clearer when
you see the code.
11. Because all of the classes rely on code in the base class, you should start by
defining the CNumberString class. This class will be the most complex in this
hierarchy, so you will walk through it step-by-step. First, right-click on your
project in the Solution Explorer and select Add Class from the Add submenu.
Name the class PhoneDatatypes.vb.

×