So far, I’ve explained that a class can be considered robust if it always contains valid data. The primary way to achieve this goal is to provide Property procedures and methods that permit the outside code to transform the internal data only from one valid state to another valid state. In this reasoning, however, is a dangerous omission: What happens if an object is used immediately after its creation? You can provide some useful initial and valid values in the Class_Initialize event procedure, but this doesn’t ensure that all the properties are in a valid state:
Set pers = New CPerson
Print pers.CompleteName ‘ Displays an empty string.
In more mature OOPLs such as C++, this issue is solved by the language’s ability to define a constructor method. A constructor method is a special procedure defined in the class module and executed whenever a new instance is created. Because you define the syntax of the constructor method, you can force the client code to pass all the values that are needed to create the object in a robust state from its very beginning, or refuse to create the object at all if values are missing or invalid.
Alas, Visual Basic completely lacks constructor methods, and you can’t prevent users of your class from using the object as soon as they create it. The best you can do is create a pseudo-constructor method that correctly initializes all the properties and let other programmers know that they can initialize the object in a more concise and robust way:
Friend Sub Init(FirstName As String, LastName As String)
Me.FirstName = FirstName
Me.LastName = LastName
End Sub
Your invitation should be gladly accepted because now the client code can initialize the object in fewer steps:
Set pers = New CPerson
pers.Init "John", "Smith"
Two issues are worth noting in the preceding code. First, the scope of the method is Friend: This doesn’t make any difference in this particular case, but it will become important when and if the class becomes Public and accessible from the outside, as we’ll see in Chapter 16. In Standard EXE projects, Friend and Public are synonymous; using the former doesn’t hurt, and you’ll save a lot of work if you later decide to transform the project into an ActiveX component.
The second noteworthy point is that arguments have the same names as the properties they refer to, which makes our pseudo-constructor easier to use for the programmer who already knows the meaning of each property. To avoid a name conflict, inside the procedure you refer to the real properties using the Me keyword. This is slightly less efficient but preserves the data encapsulation and ensures that any validation code will be properly executed when the constructor routine assigns a value to properties.
The concept of constructors can be refined by using optional arguments. The key properties of our CPerson class are undoubtedly FirstName and LastName, but in many cases the client code will also set BirthDate and ID. So why not take this opportunity to make life easier for the programmer who uses the class?
Friend Sub Init(FirstName As String, LastName As String, _
Optional ID As Variant, Optional BirthDate As Variant)
Me.FirstName = FirstName
Me.LastName = LastName
If Not IsMissing(ID) Then Me.ID = ID
If Not IsMissing(BirthDate) Then Me.BirthDate = BirthDate
End Sub
In this case, you must adopt optional arguments of type Variant because it is essential that you use the IsMissing function and bypass the assignment of values that were never provided by the client:
pers.Init "John", "Smith", , "10 Sept 1960"
You can do one more thing to improve the class’s usability and acceptance by other programmers. This point is really important because if you convince the user of your class to call the constructor you provide—and you must choose this "softer" approach, since you can’t force them to—your code and the entire application will be more robust. The trick I’m suggesting is that you write a constructor function in a BAS module in your application:
Public Function New_CPerson(FirstName As String, LastName As String, _
Optional ID As Variant, Optional BirthDate As Variant) As CPerson
‘ You don’t even need a temporary local variable.
Set New_CPerson = New CPerson
New_CPerson.Init FirstName, LastName, ID, BirthDate
End Function
Procedures of this type are sometimes called factory methods. Now see how this can streamline the portion of the client code that creates an instance of the class:
Dim pers As CPerson
‘ Creation, initialization, and property validation in one step!
Set pers = New_CPerson("John", "Smith", , "10 Sept 1960")
Tip You can reduce the typing and the guesswork when using these sort-of constructors if you gather them into a single BAS module and give this module a short name, such as Class or Factory. (You can’t use New, sorry.) Then when you need to type the name of a constructor method, you just type Class and let Microsoft IntelliSense guide you through the list of constructor methods contained in that module. You can use this approach anytime you don’t remember the name of a procedure in a module.
Creating all your objects by means of explicit constructors has other benefits as well. For example, you can easily add some trace statements in the New_CPerson routine that keeps track of how many objects were created, the initial values of properties, and so on. Don’t underestimate this capability if you’re writing complex applications that use many class modules and object instances.
Saturday, December 26, 2009
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment