Before writing code, you should take a moment to decide what data the class needs to contain and what actions a developer (whether it is you or a teammate) would want to perform on that data. For the moment, we'll keep it fairly simple by limiting the class to properties representing the columns in the Customers table and to methods that insert, delete, and update data.
Visual Basic .NET has a specific kind of code file that you can use to describe the public members of a class: an interface. An interface simply lists all the public variables, methods, and properties that any class implementing that interface must expose. Interfaces are an enormously powerful and flexible feature of object-oriented programs, and this chapter just scratches the surface. Because of the fact that an interface has no code, you can focus on the design of your application without the clutter of hundreds of lines of source code.
First, create a new Windows Application project and name it CustomerClasses.
To create a new interface, add a new class to your project by right-clicking on the Solution Explorer window and selecting Add Class. Name the new file CustomerInterface9_1.vb.
Change the class block declaration so that it reads interface instead of class, and name the class ICustomer. You should have a code file like that shown in Listing 9.1.
Public Interface ICustomer End Interface
The first items you need to add to your interface are properties to represent the columns in the Customers table.
Using the Server Explorer, drill down to the Customers table in the Northwind database so that you have an easy reference to the Customers table.
To make your code more readable, add a #Region/#End Region block with the name Properties so that you can expand and collapse your property declarations as needed.
For each column in the table, add a property declaration to your interface that matches the datatype of the column and its name(see Listing 9.2).
Public Interface ICustomer #Region "Properties" 'Define properties for this interface 'that match the table structure in name and data type Property CustomerID() As String Property CompanyName() As String Property ContactName() As String Property ContactTitle() As String Property Address() As String Property City() As String Property Region() As String Property PostalCode() As String Property Country() As String Property Phone() As String Property Fax() As String #End Region End Interface
ICustomer is a simple interface. It has only one kind of datatype-a string-and all of the properties are read/write. When you begin to write more complex classes, you might need more than just read/write properties. Visual Basic .NET also has read-only and write-only properties, parameterized properties, and default properties.
If you have written classes in Visual Basic 6, you might be scratching your head, wondering how the code sample in Listing 9.2 declares your properties read/write. In Visual Basic 6, all that mattered was whether you wrote the appropriate Property Get and Property Let/Set blocks in your class. If you did not have a Public or Friend Property Let/Set, the property was read-only. If you did not have a Public or Friend Property Get, the property was write-only.
In Visual Basic .NET, you must explicitly declare a property as read-only or write-only using the ReadOnly and WriteOnly keywords (see Table 9.1).
Object |
Purpose |
---|---|
ReadOnly |
The property cannot be modified outside of the class. |
WriteOnly |
The property can only be modified, but only methods that are internal to the class can read the property. |
In our example, a developer who is using this class could change the CustomerID. Without the original CustomerID, the class won't be able to find that record in the database when updating the table, so perhaps you should make the CustomerID property read-only. To do this, simply add the ReadOnly keyword to the beginning of the property declaration as in Listing 9.3.
'Adding the ReadOnly keyword before the property 'declaration makes the property read-only. ReadOnly Property CustomerID() As String
You can also create properties that accept a parameter. For example, in the Northwind database, a customer might have many orders. A parameterized property would be a perfect way to access Orders information from the Customer class.
A parameterized property (see Listing 9.4) takes one or more parameters that can be used to qualify the data that the property returns.
'The nIndex parameter would be used to determine which 'item from a collection of orders should be returned. Property CustomerOrders(ByVal nIndex As Integer) As COrder
Tip
One of the advantages to interfaces is the ability to rapidly define all of the classes you plan to use in your application. For the previous code example, you could create an interface called IOrder that was similar to the ICustomer interface in Listing 9.2. Instead of returning an actual class from the CustomerOrders property, you could return the IOrder interface. The interface would act as a placeholder for any object that implemented the interface. This way, you could program the Customer class and put off writing code for the Order class until later. |
In Visual Basic 6, any property in a class could be defined as the default property for that class. For example, if the CustomerID property were declared the default property, both of the examples in Listing 9.5 would return the customer ID.
Dim oCustomer As CustomerData Dim strCustomerID As String 'Example 1: This code places the value of the customer ID property 'into a variable. strCustomerID = oCustomer.CustomerID 'Example 2:This code does the same thing. strCustomerID = oCustomer
In Visual Basic .NET, the only kind of property that you can declare as the default property for a class is a parameterized property.
To declare a property as the default property, simply add the Default keyword prior to the property declaration, as shown in Listing 9.6.
'Adding the Default keyword before the property declaration 'makes this parameterized property the default property for 'the class. Default Property CustomerOrders(ByVal nIndex As Integer) As COrder
Now that you have added all the properties you need, you need to add the various methods that a developer uses to retrieve, insert, update, and delete data from the Customers table.
To make your code more readable, add a #Region/#End Region block with the name Methods so that you can expand and collapse your method declarations as needed.
The point in wrapping a table in a class is to make access to this table as simple as possible. You could have one method to insert new rows to the database and one to update existing rows, leaving the task of determining the state of the object to the code that instantiates it. The actions of the class, however, should be as transparent to other developers as possible. To this end, the interface should only define two methods: save and delete. (The retrieve method will be defined in a subsequent section.) Add the save and delete methods to your interface, as in Listing 9.7.
#Region "Methods" 'To update or delete a record, a user would have needed to 'retrieve data before modifying any of the properties. Function Save() As Boolean Function Delete() As Boolean #End Region
As I mentioned in the introduction to this chapter, one of the great advantages to wrapping up all access to a database table within a class is that you only have to write complex error-handling code once. To keep things simple, we are returning a Boolean value from each of these methods, letting other developers know whether the action failed without requiring them to write complex error-handling code.
Finally, add a ToString method to output all the data of the class in a string. This is extraordinarily useful for debugging. Your finished interface should look like Listing 9.8.
Public Interface ICustomer #Region "Properties" 'Define properties for this interface 'that match the table structure in name and data type ReadOnly Property CustomerID() As String Property CompanyName() As String Property ContactName() As String Property ContactTitle() As String Property Address() As String Property City() As String Property Region() As String Property PostalCode() As String Property Country() As String Property Phone() As String Property Fax() As String #End Region #Region "Methods" 'To update record, a user would have needed to 'retrieve data before modifying any of the properties. Function Save() As Boolean Function Delete() As Boolean Function ToString() As String #End Region End Interface
You might have noticed that an interface has no actual code. An interface is designed for one purpose: to define a set of public methods and interfaces that are related to a specific entity. To put this in object-oriented terminology, an interface represents an abstraction of an entity-in this case, a customer. Interfaces can't function by themselves; a class must implement all of its properties and methods, which is what you will do in the next sections.
Of course, you don't really need to write an interface before writing the class, and in our current example, it might be overkill. The power of an interface really comes into play when you have multiple classes that might implement the same interface in different ways.
An interface cannot exist as an object: It has no code and no place to store object data. A class that implements an interface has an explicit contract with the interface: The class must implement all the properties and methods of the interface. If you declare a variable typed to an interface, that variable could represent any object of any class that implements the interface.
You've already seen several examples of interfaces in this book, such as the DataAdapter, DataReader, Connection, Command, and other ADO.NET objects. Take, for example, the Command interface. At present, two types of Command classes exist: the OleDbCommand and the SqlCommand. Both of these classes implement the IDbCommand interface. The OleDbCommand is responsible for handling data access to any database provider with an OleDB driver, and the SqlCommand connects directly to SQL Server. If you wrote a function that processed Command objects-such as a debugging function that wrote all the properties of a Command to an error log-you might need to handle both kinds. Instead of writing two functions, one for each Command type,
Public Sub WriteCommandErrorToLog(ByVal pCommand as SqlCommand) Public Sub WriteCommandErrorToLog(ByVal pCommand as OleDbCommand)
you could simply declare a function that takes a variable of type IDbCommand, which would accept an instance of either command type:
Public Sub WriteCommandErrorToLog(ByVal pCommand as IDbCommand)
The second solution is far more elegant because, before long, you could end up with Commands for Oracle, Sybase, MySql, SqlAnywhere, DB2, and so on, all of which would implement the IDbCommand interface.
It might look like you're missing a few items in ICustomer. What method do you call to add new records or retrieve data from the database? If you're working with a new customer row, how does a developer set the CustomerID? A new type of method called a constructor is executed when a class is instantiated. Because constructors can't be declared in an interface, we will cover this subject in section 9.4.