Beginning Visual Basic 2005 (2006)
.pdf
Chapter 10
You might also want to be able to control the object, for example:
Tell it to accelerate.
Tell it to decelerate.
Tell it to turn left.
Tell it to turn right.
Tell it to straighten out of a turn.
Tell it to do a three-point-turn.
Tell it to stop completely.
There are three concepts of an object that you need to be aware of: identity, state, and behavior. We’ll assume that the identity aspect is covered because you know what the class is, so the state and behavior are of interest here.
State
State describes facts about the object now. For example, a car’s location and speed are part of its state. When designing objects, you need to think about what aspects of state you need to handle. It might not be useful to know a customer’s speed, for example, but you might well want to know that customer’s current address.
State tends to be implemented as values inside an object. Some of these values will be publicly available through properties, and some will be private. Also, some aspects of state might be publicly readable but not changeable. For example, cars have a speedometer that is readable to anybody using the car. But you can’t change the car’s speed by playing with the speedometer — you need to alter the car’s behavior by using the brake or accelerator.
Behavior
While a car might have a read-only Speed property, it would have methods to accelerate and decelerate. When you invoke an object’s method, you are telling your object to do something — so behavior is usually associated with methods. Properties can also be associated with behavior. When you set a property to a particular value (such as by changing the setting of a control), you can trigger behavior.
Behavior is implemented as a set of Visual Basic 2005 statements that do something. This will usually involve one or both of the following:
Changing its own state: When you invoke the accelerate method on a car, it should get faster if it is capable of doing so.
Somehow affecting the “world” outside the object: This could be manipulating other objects in the application, displaying something to the user, saving something to a disk, or printing a document.
In this chapter, you won’t build all of the properties and methods discussed. Instead, you’ll build a handful of the more interesting ones. You begin in the following Try It Out by creating your new project and the Car class.
316
Building Objects
Try It Out |
Creating a New Project and the Car Class |
1.Start Visual Basic 2005 and select File New Project from the menu.
2.When the New Project dialog box appears, select the Console Application template and enter the name of the project as Objects. Click OK to create the project.
3.You now need to create a new class. This is done through the Solution Explorer, so right-click on the Objects project and select Add Class. This will prompt you for a new class name, so enter Car.vb as the class name and click Add. The new class has been added to the Solution Explorer and the editor now shows the code listing for it, albeit empty.
Storing State
State describes what the object understands about itself, so if you give a car object some state, for example, “You are blue,” you’re giving the car object a fact: “The car I represent is blue.”
So how do you actually manage state in your classes? Well, state is typically held in variables, and you define those variables within the class. You see how to do this in a moment.
Usually, the methods and properties you build will either affect or use the state in some way. Imagine you’ve built a property that changes the color of the car. When you set that property, the variable that’s responsible for storing the state will be changed to reflect the new value that it has been given. When you retrieve (get) that property, the variable responsible for storing the state will be read, and the current value will be returned to the caller.
In a way, then, properties are behavior. Under the hood, a public property has two methods: a Get method and a Set method (defined by Get . . . End Get and Set . . . End Set blocks of code, as you have already encountered in Chapter 5). A simple Get method for the Color property will contain code to tell the caller what color the car is. A simple Set method for the Color property will set a value that represents the car’s color. In a real application, though, Color would probably mean something more than just remembering a value. In a driving game, for example, the Set method of the Color property would need to make the screen display change the color in which the car is shown on the screen.
When a property has no behavior at all, you can cheat. In the next Try It Out, you create a Color “property” by declaring a Color variable and making it public. When a property is implemented like this, it is also called a field. Although this can be a useful and very fast technique for adding properties, declaring a field instead of the Property, Get, and Set blocks is not actually recommended, but for this small example it is just fine.
Try It Out |
Creating an Object and Adding a Color Property |
1.In the Car class, add this code:
Public Color As String
2.That’s it! However, you do need a way of consuming the class so that you can see it working. Open Module1.vb and add this code:
Sub Main()
‘Create a new car object
317
Chapter 10
Dim objCar As New Car
‘Set the Color property to Red objCar.Color = “Red”
‘Show what the value of the property is
Console.WriteLine(“My car is this color:”)
Console.WriteLine(objCar.Color)
‘Wait for input from the user Console.ReadLine()
End Sub
3.Now run the project. A new window will appear similar to Figure 10-1.
Figure 10-1
4.Press Enter to end the program.
How It Works
Defining the field is easy. The line of code
Public Color As String
tells the class that you want to create a variable called Color and you want the field to hold a string of text characters. The use of the Public keyword when you declare the Color variable tells the class that the variable is accessible to developers using the Car class, not only from within the class itself.
Variables defined in the location between the Public Class and End Class lines, but outside of any functions, are known as member variables in the class itself and as fields to consumers of the class.
Using the object is simple, and you do this from within Module1.vb. This process actually takes two steps. First, you have to declare a variable to refer to an object for the class; second, you instantiate the object. The following line of code creates an object variable called objCar and tells it that it’s going to hold exclusively any objects created using the Car class:
Dim objCar As Car
When you define the variable, it doesn’t yet have an object instance associated with it; you are simply identifying the type of object. It’s a bit like telling the computer to give you a hook that you can hang a Car object on, and call the hook objCar. You haven’t hung anything on it yet — to do that you have to create an instance of the class. This is done using the New keyword:
Set objCar = New Car
318
Building Objects
But Visual Basic 2005 allows you to combine both steps into one line of code:
‘Create a new car object Dim objCar As New Car
So, what you’re saying here is: “let objCar refer to a newly created object instantiated from the class Car.” In other words, “create a new car and hang it on the hook called objCar.” You now have a Car object and can refer to it with the name objCar.
Note that in OO programming, the same object can be hanging on several different hooks at the same time and, therefore, have several different names. This might seem confusing, but in most cases it is a really intuitive way to work. Imagine how cool it would be if your keys could be on several hooks at the same time — they’d be so much easier to find!
After you have an object instance, you can set its properties and call its methods. Here is how you set the Color property:
‘Set the Color property to Red objCar.Color = “Red”
Once the property has been set, it can be retrieved again as many times as you want or its value changed at a later point. Here, retrieval is illustrated by passing the Color property to the WriteLine method on the Console class:
‘Show what the value of the property is Console.WriteLine(“My car is this color:”) Console.WriteLine(objCar.Color)
The Console.ReadLine line means that the program does not continue until you press Enter. Basically the console window is waiting for input from you.
‘Wait for input from the user Console.ReadLine()
Console applications are a good way to test in-memory objects because you don’t need to worry about setting up a user interface. You can just display lines of text whenever you want. The objects you build will work just as well in a Windows application, though.
Even though this is not really a property from the point of view of a developer using the class, it works just like one. In fact, “real” properties are methods that look like variables to users of the class. Whether you use a method or a property really depends on what the users of your class will find easier. You’ll start to see this in the next section.
Real Properties
Now that you’ve seen how to cheat, let’s see how to do things properly. The property you saw can be set to pretty much anything. As long as it’s a string, it will be accepted. Also, setting the property doesn’t do anything except change the object’s internal state. Often you want to control what values a property can be set to; for example, you might have a list of valid colors that a car can be. You might also want to
319
Chapter 10
associate a change to a property with a particular action. For example, when you change a channel on the TV, you want it to do a bit more than just change its mind about what channel it’s displaying. You want the TV to show a different picture! Just changing the value of a variable won’t help here.
Another reason to use real properties is that you want to prevent the user of the class from directly changing the value. This is called a read-only property. The car’s speed is a good example of how a class that models a real-world object should behave like that real-world object. If you are going 60 mph, you cannot simply change the speed to a value you prefer. You can read the speed of a car from the speedometer,
but you cannot change (write) the speed of the car by physically moving the needle around the dial with your finger. You have to control the car in another fashion, which you do by stepping on the gas pedal to accelerate or on the brake pedal to decelerate. To model this feature in the Car class, you use methods
(Accelerate, Decelerate) that affect the speed and keep a read-only property around called Speed that will report on the current speed of the vehicle.
You’ll still need to keep the speed around in a member variable, but what you need is a member variable that can be seen or manipulated only by the class itself. You accomplish this by using the Private keyword:
Private _speed As Integer
The _speed variable is marked as Private and can, therefore, be accessed only by functions defined inside the class itself. Users of Car will not even be aware of its presence. In the style we use and recommend in this book, private members are camelCased rather than PascalCased, so that you can easily tell whether something is public or private when you use it. When a private variable maps directly to a public property, you prefix the variable name with an underscore (_).
Now you’ll see how you can build a property that will give the user of the object read-only access to the car’s speed.
Try It Out |
Adding a Speed Property |
1.To define a private variable, use the Private instead of the Public keyword. Add this statement to the Car class:
Public Color As String
Private _speed As Integer
2.To report the speed, you need to build a read-only property. Add this code to your Car class:
‘Speed - read-only property to return the speed Public ReadOnly Property Speed() As Integer
Get
Return _speed
End Get
End Property
3.Now, you build a method called Accelerate that will adjust the speed of the car by however many miles-per-hour you give it. Add this code after the Speed property:
‘Accelerate - add mph to the speed
Public Sub Accelerate(ByVal accelerateBy As Integer)
320
Building Objects
‘Adjust the speed _speed += accelerateBy
End Sub
4.To test the object, you need to make some changes to the Main procedure in Module1. Open the file and modify the code as shown:
Sub Main()
‘Create a new car object Dim objCar As New Car
‘Report the speed Console.WriteLine(“The car’s speed is:”) Console.WriteLine(objCar.Speed)
‘Accelerate
objCar.Accelerate(5)
‘Report the new speed Console.WriteLine(“The car’s speed is now:”) Console.WriteLine(objCar.Speed)
‘Wait for input from the user Console.ReadLine()
End Sub
5.Now run the project. A new window will appear similar to Figure 10-2.
Figure 10-2
How It Works
The first thing you do is define a private member variable called _speed in the Car class:
Private _speed As Integer
By default, when the object is created, _speed will have a value of zero because this is the default value for the Integer data type.
You then define a read-only property that returns the current speed:
‘Speed - readonly property to return the speed Public ReadOnly Property Speed() As Integer
Get
Return _speed End Get
End Property
321
Chapter 10
When you define properties, you can set them to be read-only (through the ReadOnly keyword), writeonly (through the WriteOnly keyword), or both readable and writable by using neither. Reading a property is known as getting the value, whereas writing to a property is known as setting the value. The code between Get and End Get will be executed when the property is read. In this case, the only thing you’re doing is returning the value currently stored in _speed.
You also created a method called Accelerate. This method doesn’t have to return a value, so you use the Sub keyword:
‘Accelerate - add mph to the speed
Public Sub Accelerate(ByVal accelerateBy As Integer) ‘Adjust the speed
_speed += accelerateBy End Sub
The method takes a single parameter called accelerateBy, which you use to tell the method how much to increase the speed by. You’ll notice that the only action of the method is to adjust the internal member _speed. In real life, the pressure on the accelerator pedal, along with factors such as wind speed and road surface, will affect the speed. The speed will be an outcome of several factors — not something you can just change. You need some complex code to simulate this. Here you are just keeping things simple and incrementing the _speed variable with the value passed to the method.
Accelerating a car is another example of encapsulation. To accelerate the car in a real-world implementation you need an actuator of some kind to open the throttle further until the required speed is reached. As consumers of the object, you don’t care how this is done. All you care about is how to tell the car to accelerate.
Consuming this new functionality is simple. First, you create the variable and instantiate the object as you did in the last exercise:
‘Create a new car object Dim objCar As New Car
Next, you write the current speed:
‘Report the speed Console.WriteLine(“The car’s speed is:”) Console.WriteLine(objCar.Speed)
Notice how you’re using the read-only Speed property to get the current speed of the car. When the object is first created, the internal _speed member will be set at 0.
Now you call Accelerate and use it to increase the speed of the car:
‘Accelerate
objCar.Accelerate(5)
Finally, you write out the new speed:
‘Report the new speed Console.WriteLine(“The car’s speed is now:”) Console.WriteLine(objCar.Speed)
322
Building Objects
Read/Write Properties
So, why would you need to use the Property keyword to define properties that are both readable and writable if you can achieve the same effect with a line like this?
Public Color As String
Well, if you build the property manually using the Property keyword, you can write code that is executed whenever the property is set or gotten. This is extremely powerful!
For example, the Property keyword allows you to provide validation for new values. Imagine you had a property called NumberOfDoors. You wouldn’t want this to be set to nonsense values like 0 or 23453. Rather, you would have some possible range. For modern cars this is going to range from 2 to 5.
This is an important consideration for developers building objects. It’s imperative that you make life as easy as possible for a developer to consume your object. Dealing with problems like making sure a car can’t have 10 million doors is an important aspect of object design.
Likewise, you might not have the information to return to the consumer of your object when you are asked to return the property; you might have to retrieve the value from somewhere, or otherwise calculate it. You might have a property that describes the total number of orders a customer has ever made or the total number of chew toys a dog has destroyed in his life. If you build this as a property, you can intercept the instruction to get the value and find the actual value you require on demand from some other data store, such as a database or a Web service. You’ll see this covered in later chapters.
For now, let’s deal with the number-of-doors problem.
Try It Out |
Adding a NumberOfDoors Property |
1.The first thing you need to do is build a private member that will hold the number of doors. You’re going to define this member as having a default of 5. Add this code in the Car class as highlighted below:
Public Color As String
Private _speed As Integer
Private _numberOfDoors As Integer = 5
2.Now you can build a property that will get and set the number of doors, provided the number of doors is always between 2 and 5. Add this code to your Car class directly beneath the
Accelerate method:
‘NumberOfDoors - get/set the number of doors Public Property NumberOfDoors() As Integer
‘Called when the property is read Get
Return _numberOfDoors End Get
‘Called when the property is set Set(ByVal value As Integer)
‘Is the new value between two and five
323
Chapter 10
If value >= 2 And value <= 5 Then _numberOfDoors = value
End If End Set
End Property
In this chapter, you’re going to ignore the problem of telling the developer if the user has provided an invalid value for a property. Ideally, whenever this happens, you need to throw an exception. The developer will be able to detect this exception and behave accordingly. (For example, if the user typed the number of doors as 9999 into a text box, the program could display a message box telling the user that they have provided an invalid value for the number of doors, since no car has that many doors.) You learned about exception handling in Chapter 9.
3.To test the property, you need to change the Main procedure in Module1 by modifying the code as indicated here:
Sub Main()
‘Create a new car object Dim objCar As New Car
‘Report the number of doors Console.WriteLine(“The number of doors is:”) Console.WriteLine(objCar.NumberOfDoors)
‘Try changing the number of doors to 1000 objCar.NumberOfDoors = 1000
‘Report the number of doors Console.WriteLine(“The number of doors is:”) Console.WriteLine(objCar.NumberOfDoors)
‘Now try changing the number of doors to 2 objCar.NumberOfDoors = 2
‘Report the number of doors Console.WriteLine(“The number of doors is:”) Console.WriteLine(objCar.NumberOfDoors)
‘Wait for input from the user Console.ReadLine()
End Sub
Try running the project. You should see a screen like the one in Figure 10-3.
Figure 10-3
324
Building Objects
How It Works
First you define a private member variable called _numberOfDoors. You also assign the default value of 5 to this variable.
Private _numberOfDoors As Integer = 5
The motivation behind setting a value at this point is simple: You want _numberOfDoors to always be between 2 and 5. When the object is created, the _numberOfDoors will be assigned a value of 5. Without this assignment, _numberOfDoors would have a default value of 0. This would be inconsistent with the understanding that the number of doors must always be between 2 and 5, so you guard against it.
Next comes the property itself. The Get portion is simple — just return the value held in _numberOfDoors — but the Set portion involves a check to ensure that the new value is valid. The new value is passed in through a parameter called value:
‘NumberOfDoors - get/set the number of doors Public Property NumberOfDoors() As Integer
‘Called when the property is read Get
Return _numberOfDoors End Get
‘Called when the property is set Set(ByVal value As Integer)
‘Is the new value between two and five If value >= 2 And value <= 5 Then
_numberOfDoors = value End If
End Set End Property
The test code you add to Module1 is not very complex. You simply display the initial value of _number OfDoors and then try to change it to 1000. The validation code in the NumberOfDoors property won’t change the _numberOfDoors member variable if an inconsistent number is used, so when you report the number of doors again, you find it hasn’t changed from 5. Lastly, you try setting it to 2, which is a valid value, and this time, when you report the number of doors, you get an output of 2.
Even though read–write properties and public variables seem to work the same way, they are very different. When your Visual Basic 2005 code is compiled, the compiled code treats property calls as a call to a method. Always using properties instead of public variables makes your objects more flexible and extendable. Of course, using public variables is easier and quicker. You need to decide what is most important in each case.
The IsMoving Method
When building objects you should always have the following question in the back of your mind. “How can I make this object easier to use?” For example, if the consumer needs to know whether the car is moving, what would be the easiest way to determine this?
325
