- Designing a Public Class Interface
- Protecting Class Members
- Defining Private Class Members
- Designing Objects Using Encapsulation
- Summary
- Q&A
- Workshop
Designing Objects Using Encapsulation
So far, the information provided has been used in simple examples. By now, you should understand encapsulation principles but probably don't have a firm grasp on how it's used in object design and the creation of real-world object classes.
Think about the real-world instance of an application that deals with a database. This example uses a book database that stores a list of books and authors. The application you are creating deals with the list of books in several different areas of the application, and some developers working on the application don't have much knowledge about databases.
The goal is to design a class that encapsulates a list of books and all the functionality required to query the list from the database while providing a simple interface to retrieve the data. With this class, any area of the application and any developer could retrieve the list of books without any knowledge of the underlying database or how it works. Also, the database code could change, and it would require a single class to change instead of several modules in a large application.
The design of class Books is shown in the UML diagram in Figure 3.1. The BookData class is defined within the Books class as Private and isn't accessible for use outside the Books class.
Figure 3.1 UML class diagram of Books class.
Without getting into how things are implemented in the Books class, concentrate on the public interface of the class. Listing 3.4 has all the public interfaces defined for the Books class.
Listing 3.4 Book.vb: Books Class Definition of Public Interface
Public Class Books Implements IDisposable 'Private members for internal usage Private SqlCommand As SqlClient.SqlDataAdapter Private QueryData As BookData Private CurrentRow As Int32 'Public New() interface for constructing the object Public Sub New() strTitle = Nothing strAuthor = Nothing dPrice = 0 CurrentRow = -1 SqlCommand = New SqlClient.SqlDataAdapter() SqlCommand.SelectCommand = New SqlClient.SqlCommand() SqlCommand.TableMappings.Add("Table", BookData.BOOK_TABLE) End Sub 'Public dispose interface Public Overridable Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(True) End Sub 'Public Query method provides the user of the object a method to 'start the query of the books in the database Public Sub Query() If Not QueryData Is Nothing Then QueryData.Dispose() End If CurrentRow = 0 QueryData = New BookData() With SqlCommand Try With .SelectCommand .CommandType = CommandType.Text .CommandText = "SELECT title, au_lname, price FROM " & _ BookData.BOOK_TABLE .Connection = New System.Data.SqlClient.SqlConnection( _ "data source=(local)\NetSDK;initial catalog=pubs;" & _ "persist security info=False;user id=sa;") End With .Fill(QueryData) Finally If Not .SelectCommand Is Nothing Then If Not .SelectCommand.Connection Is Nothing Then .SelectCommand.Connection.Dispose() End If .SelectCommand.Dispose() End If .Dispose() End Try End With End Sub 'The public Fetch method provides the object user a method 'of fetching each record that was returned from the query 'The results are loaded into members for access via public properties Public Function Fetch() As Boolean Fetch = False If CurrentRow < 0 Then Exit Function End If With QueryData.Tables(BookData.BOOK_TABLE).Rows 'If there are no more records, exit If CurrentRow >= .Count() Then CurrentRow = -1 Exit Function End If Try strTitle = CType(.Item(CurrentRow).Item(BookData.TITLE_COLUMN), _ String) strAuthor = CType(.Item(CurrentRow).Item(BookData.AUTHOR_COLUMN), _ String) dPrice = CType(.Item(CurrentRow).Item(BookData.PRICE_COLUMN), _ Double) Catch 'Do nothing... Finally Fetch = True CurrentRow += 1 End Try End With End Function #Region " Property Definitions " Private strTitle As String Private strAuthor As String Private dPrice As Double 'The public properties provide read only access to the 'data returned from the query after each call to Fetch() Public ReadOnly Property Title() As String Get Title = strTitle End Get End Property Public ReadOnly Property Author() As String Get Author = strAuthor End Get End Property Public ReadOnly Property Price() As String Get Price = dPrice.ToString("c") End Get End Property #End Region End Class
You can compare the class definition to the UML class diagram in Figure 3.1 and get an idea of the interface. Adding the additional protected and private code to the class as shown in Listing 3.5 completes the Books class definition.
Listing 3.5 Book.vb: Books Class Definition of Private and Protected Interfaces
Public Class Books Implements IDisposable 'Private members for internal usage Private SqlCommand As SqlClient.SqlDataAdapter Private QueryData As BookData Private CurrentRow As Int32 'Public New() interface for constructing the object Public Sub New() ... End Sub 'Public dispose interface Public Overridable Sub Dispose() Implements IDisposable.Dispose ... End Sub 'Protected dispose for internal class usage Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not disposing Then Exit Sub ' we're being collected, so let the GC take care of ' this object End If If Not SqlCommand Is Nothing Then If Not SqlCommand.SelectCommand Is Nothing Then If Not SqlCommand.SelectCommand.Connection Is Nothing Then SqlCommand.SelectCommand.Connection.Dispose() End If SqlCommand.SelectCommand.Dispose() End If SqlCommand.Dispose() SqlCommand = Nothing End If End Sub 'Public Query method provides the user of the object a method to 'start the query of the books in the database Public Sub Query() ... End Sub 'The public Fetch method provides the object user a method 'of fetching each record that was returned from the query 'The results are loaded into members for access via public properties Public Function Fetch() As Boolean ... End Function #Region " Property Definitions " Private strTitle As String Private strAuthor As String Private dPrice As Double Public ReadOnly Property Title() As String Get Title = strTitle End Get End Property Public ReadOnly Property Author() As String Get Author = strAuthor End Get End Property Public ReadOnly Property Price() As String Get Price = dPrice.ToString("c") End Get End Property #End Region 'The BookData class provides a DataSet that represents the data 'returned from the query <SerializableAttribute()> Private Class BookData Inherits DataSet Public Const BOOK_TABLE As String = "TitleView" Public Const TITLE_COLUMN As String = "title" Public Const AUTHOR_COLUMN As String = "au_lname" Public Const PRICE_COLUMN As String = "price" Public Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) MyBase.New(info, context) End Sub Public Sub New() MyBase.New() BuildDataTables() End Sub Private Sub BuildDataTables() ' ' Create the Books table ' Dim table As DataTable = New DataTable(BOOK_TABLE) With table.Columns .Add(TITLE_COLUMN, GetType(String)) .Add(AUTHOR_COLUMN, GetType(String)) .Add(PRICE_COLUMN, GetType(Double)) End With Me.Tables.Add(table) End Sub End Class End Class
The Books class obviously has a significant amount of code to provide the implementation logic needed by design. The good part is that the Books class user has very little code to write to retrieve a list of books. The use of the Books class is shown in the following code segment:
Public Sub MySub() Dim BookQuery As Books = New Books() BookQuery.Query() While BookQuery.Fetch() 'Do something with data by accessing the properties within 'the Books object End While End Sub
By building classes, such as Books, and encapsulating as much functionality as possible within the class, you can create complex applications with little code.
When a new instance of the Books class is created and assigned to the BookQuery object, all the initialization in the Books.New() constructor is executed. You can see how much code you would have to write repeatedly to duplicate the functionality without the Books class.
When the Books.Query() method is called, a SQL connection is made to a SQLServer, and a query is executed to bring back a list of books with author names and prices. The query results are saved in a DataSet for future processing. Users of the Books object execute only one statement while the class implementation takes care of all the dirty work in dealing with the database.
Finally, the users process the results of the query by repeatedly calling the Books.Fetch() method until it returns false. After each call to Fetch(), the Books object properties are set to the current record, and users can retrieve those values with the property methods.