In our previous example, our Address property of the Student class is responsible for proper format of the Student address. For instance, let us pretend that it takes a lot of processing time to evaluate its result and return the value. If you trace the execution of the program it executes all the Property Get in order to construct the correct address format, which we're surely like that the class would do. But add the Debug.Print statement below:
' Other client form code omitted ' Show Student information in the client form MsgBox Student.StudentInfo(, True) ' Call the Student StudentAddressInfo method Debug.Print Student.StudentAddressInfo ' Add this code |
The code above demonstrate the overhead of calling Student address even if we don't change address information of the student. In other words, the class (Address) always evaluate (run) the Property Get even if it is not necessary to do so because it highly dependent on the independet (such as Street, City, State, Zip and Country) Property value. So how can we modify this function to keep the overhead to a minimum without modifying the interface that the class exposes to the outside. A common sense solution we case use is don't to reevaluate it (all independent property, such as Street, City, State, Zip and Country) each time the client code makes a request. Right! But how? We can store the return value in a Private Variant variable before returning to the client and reuse that value if possible in all subsequent calls. The trick works because each time either Street, City, State, Zip or Country are assigned a new value, the Private variable is cleared, which forces it to be reevaluated the next time the CompleteAddress function is invoked.
' In Address class module declaration ' Other Private member variable omitted Private m_CompleteAddress As Variant Public Property Let Street(ByVal strNewStreet As String) If Len(strNewStreet) = 0 Then Err.Raise 5 m_Street = strNewStreet m_CompleteAddress = Empty ' add this line in every Property Let Procedures End Property ' Other Property Let procedure ommited ' Revised CompleteAddress Method Public Function CompleteAddress() As String If IsEmpty(m_CompleteAddress) Then m_CompleteAddress = Street & vbCrLf & City & ", " & _ State & " " & Country & " " & Zip End If CompleteAddress = m_CompleteAddress End Function |
If trace again the program execution (Pressing F8), the second time you invoke the CompleteAddress method (via StudentAddressInfo method of Student class), the class smartly save the previous result and return it. You might ask "We implement this technique to the Address object, why we did not implement this to Student class, is it possible?" Yes of course, but you must understand that a class should be robust, and to be a robust class, the class should be responsible for himself! As you can see, even if the CompleteAddress method are highly dependent on independent properties, it access it dependent property in Address class in which CompleteAddress is also included. Now lets go back to the topic, one last note, don't underestimate the advantage of this technique, because in a real-world application, this difference might involve unnecessarily opening a database, reestablishing a remote connection, and so on.
Initialization Method
We already explained that the class to be robust, it must always contains a valid value. And to achieve this objectives, we provide our class a Property procedures and methods to transform the internal data of the class to a valid state by providing validation code inside this procedures. However, if you are familiar with C++ and Java, you might be asking, what if an object is used immediately after creation of the object or during the creation of the object in the client side and how can we provide the user or client a initial valid values? We can provide the client some useful initial valid value in theClass_Initialize event procedure, without having to specify it in the client code. Visual Basic offers a neat way by writing some statements in the Class_Initialize event of the class module. To have the editor create a template for this event procedure, you select the Class item in the leftmost combo box in the code editor. Visual Basic automatically selects the Initialize item from the rightmost combo box control and inserts the template into the code window.
Because we are dealing with the Student class object, you can provide a reasonable value for client to expect in its Country property of the Address object property of the Student. For example, "Philippines" or whatever nationality appropriate where you live. In this, you would like for these default values to be assigned when you create an object, rather than, having assign them manually in the code that uses the class.
' In Address class module Private Sub Class_Initialize() m_Country = "Philippines" End Sub |
If you trace the program, you will see that as soon as Visual Basic creates the object (the Set command in the form module), the Class_Initialize event fires. The object is returned to the caller with all the properties correctly initialized, and you don't have to assign them in an explicit way.
But this solution might be not enough for us. We just solve the second problem stated above. What happens if an object is used immediately after its creation. Consider this example:
' In the Client form Set Student = New Student Debug.Print Student.FirstName ' << this will display nothing Debug.Print Student.FullName ' << this will raise an error |
In other programming language, this problem is solved by defining a special procedure that is defined in the class module and executed whenever a new instance is create, just like C++ and Java constructor. Because Visual Basic completely lack of constructor method, you can't prevent the user of your class from using the object as soon as they create it. The best solution that you can do is create simulated constructor method that correctly initialize all (if you desire) the properties and let the user know that they can initialize the object in a short way.
' In the Address class module Public Sub InitAddress(Optional ByVal Street As Variant, _ Optional ByVal City As Variant, _ Optional ByVal State As Variant, _ Optional ByVal Zip As Variant, _ Optional ByVal Country As Variant) If Not IsMissing(Street) Then Me.Street = Street If Not IsMissing(City) Then Me.City = City If Not IsMissing(State) Then Me.State = State If Not IsMissing(Zip) Then Me.Zip = Zip If Not IsMissing(Country) Then Me.Country = Country End Sub ' In the Student class module Public Sub InitStudent(Optional ByVal StudentID As Variant, _ Optional ByVal FirstName As Variant, _ Optional ByVal LastName As Variant, _ Optional ByVal Major As MajorCodeEnum = Freshmen, _ Optional ByVal YearLevel As YearLevelEnum = BSCS, _ Optional ByVal BirthDate As Variant, _ Optional ByVal Gender As GenderEnum = Male, _ Optional ByVal Address As Variant, _ Optional ByVal ProvincialAddress As Variant) If Not IsMissing(StudentID) Then Me.StudentID = StudentID If Not IsMissing(FirstName) Then Me.FirstName = FirstName If Not IsMissing(LastName) Then Me.LastName = LastName If Not IsMissing(Major) Then Me.Major = Major If Not IsMissing(YearLevel) Then Me.YearLevel = YearLevel If Not IsMissing(BirthDate) Then Me.BirthDate = BirthDate If Not IsMissing(Gender) Then Me.Gender = Gender If Not IsMissing(Address) Then _ Set Me.Address = Address ' Set command is necessary If Not IsMissing(ProvincialAddress) Then _ Set Me.ProvincialAddress = ProvincialAddress ' also here End Sub |
Now you can tell the user of your class, to use your newly created simulated constructor:
' In the Client form ' Initiate the object Student Set Student = New Student Set Address = New Address Set ProvincialAdd = New Address ' Set up Address Address.InitAddress "Block 10 Lot 26, Molave Street, Calendola Village", _ "San Pedro", _ "Laguna", _ "4023" ' Set up Provincial address ProvincialAdd.InitAddress "Block 10 Lot 26, Molave Street, Calendola Village", _ "San Pedro", _ "Laguna", _ "4023" ' Set up Student Student.InitStudent "12345", "Dante", "Salvador", _ BSCS, Senior, #10/24/1972#, Male, _ Address, ProvincialAdd ' Other code omitted |
As you can see, we 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. The good consequence of this approach is that, we can use default value to the parameter list as shown in InitStudent method. We also use the same names of the properties they refer to, this makes the method easier to use and to avoid name conflict inside the procedure, we use Me keyword to refer to the real properties of the class.
Now, to add more usability of your class, you can provide a function in a BAS module in your application that return a newly created object of your class:
' In the Standard module of your application Public Function New_Student(Optional ByVal StudentID As Variant, _ Optional ByVal FirstName As Variant, _ Optional ByVal LastName As Variant, _ Optional ByVal Major As MajorCodeEnum = Freshmen, _ Optional ByVal YearLevel As YearLevelEnum = BSCS, _ Optional ByVal BirthDate As Variant, _ Optional ByVal Gender As GenderEnum = Male, _ Optional ByVal Address As Variant, _ Optional ByVal ProvincialAddress As Variant) As Student ' Initiate an object Student Set New_Student = New Student ' Call InitStudent method New_Student.InitStudent StudentID, FirstName, LastName, MAJOR_CODE_MAX, _ YearLevel, BirthDate, Gender, _ Address, ProvincialAddress End Function Public Function New_Address(Optional ByVal Street As Variant, _ Optional ByVal City As Variant, _ Optional ByVal State As Variant, _ Optional ByVal Zip As Variant, _ Optional ByVal Country As Variant) As Address ' Initiate an Address object Set New_Address = New Address ' Call InitAddress method New_Address.InitAddress Street, City, State, Zip, Country End Function |
See how concise your code in the client form:
' In client form ' Declare Student object Dim Student As Student Dim Address As Address Dim ProvincialAdd As Address ' Initiate and create Address object Set Address = New_Address("Block 10 Lot 26, Molave Street, " & _ "Calendola Village", _ "San Pedro", _ "Laguna", _ "4023") ' Initiate and create Provincial Address object Set ProvincialAdd = New_Address("Block 10 Lot 26, Molave Street, " & _ "Calendola Village", _ "San Pedro", _ "Laguna", _ "4023") ' Initiate and create Student object Set Student = New_Student("12345", "Dante", "Salvador", _ BSCS, Senior, #10/24/1972#, Male, _ Address, ProvincialAdd) ' Show Student information MsgBox Student.StudentInfo(, True) |
You can add a little spice to your function, by assigning the Address property value to the ProvincialAddress property, if the Student lives in the same address.
' In the Standard module of your application Public Function New_Student(Optional ByVal StudentID As Variant, _ Optional ByVal FirstName As Variant, _ Optional ByVal LastName As Variant, _ Optional ByVal Major As MajorCodeEnum = Freshmen, _ Optional ByVal YearLevel As YearLevelEnum = BSCS, _ Optional ByVal BirthDate As Variant, _ Optional ByVal Gender As GenderEnum = Male, _ Optional ByVal Address As Variant, _ Optional ByVal ProvincialAddress As Variant) As Student ' Initiate an object Student Set New_Student = New Student ' Assign the same adddress if ProvincialAddress is not set If IsMissing(ProvincialAddress) Then Set ProvincialAddress = Address ' Call InitStudent method New_Student.InitStudent StudentID, FirstName, LastName, MAJOR_CODE_MAX, _ YearLevel, BirthDate, Gender, _ Address, ProvincialAddress End Function ' In client form Dim Student As Student Dim Address As Address ' Initiate and create Address object Set Address = New_Address("Block 10 Lot 26, Molave Street, " & _ "Calendola Village", _ "San Pedro", _ "Laguna", _ "4023") ' Initiate and create Student object Set Student = New_Student("12345", "Dante", "Salvador", _ BSCS, Senior, #10/24/1972#, Male, _ Address) ' Show Student information MsgBox Student.StudentInfo(, True) ' Change the provincial address if you will With Student With .ProvincialAddress .Street = "830 Euclid Avenue" .City = "Cleveland" .State = "Ohio" .Zip = "44114" .Country = "USA" End With End With ' Show Student provincial address info MsgBox Student.StudentProvincialAddInfo |
Comments