Discussion: Class CDataBase

A CDataBase object is used to encapsulate all access to the two CPersonList objects that store data for the application. The object provides a clear interface between your Windows code and the database. This section discusses the structure and use of CDataBase.

This discussion does not instruct you to add any code to your files.

Purpose and Structure of CDataBase

CDataBase and the data model from Chapter 2 make a good model for Windows programs written with C++ and the Microsoft Foundation Class Library. CDataBase preserves the independence of the data model from the user interface, but it also tailors access to the data model to the needs of a command-driven user interface, such as the program's Windows user interface.

If CDataBase had not been invented, considerably more code would have to be added directly to the main window class. The main window object would have to store two CPersonList objects in its member variables (one for the database and one to store persons found by searches), keep track of the filename for the current database, maintain information about which of the two CPersonList objects to display, keep track of unsaved changes to the database, manage file opening and I/O, and more. Each of the main window object's message-handler functions would be more complex.

With CDataBase, on the other hand, all of that database-related code is neatly encapsulated in a single database object. The main window object is much cleaner and simpler than it otherwise would be. It constructs and stores only a single CDataBase object and uses that object for all communication with the data model objects. The interface of CDataBase—its list of public member functions—is simple and tailored to the application's needs. Figure 4.2 shows the relationship of CDataBase to the Windows user interface and to the CPersonList data model.

CDataBase Member Functions

Class CDataBase declares a constructor and a number of member functions. The constructor creates and initializes a CDataBase object. As you can see from its inline definition, the CDataBase constructor sets the m_pDataList and m_pFindList member variables of class CDataBase to NULL, signifying that the database is empty, and sets the m_szFileName and m_szFileTitle member variables to an empty string value.

Some of the CDataBase member functions are directly mapped to menu commands in the Phone Book program. When the user chooses the New, Open, Save, Close, or Exit commands in the File menu, or the Add, Delete, Find, or Edit commands in the Person menu, the message-handler functions in the main window object for those menu commands call corresponding CDataBase member functions to do the bulk of the work. You'll see later how these functions are used.

The remaining member functions of CDataBase provide utility services, making the database easier to use. Of these, some are database utitlities and some are file-handling utilities.

Database Member Functions

GetPerson

Returns a pointer to the CPerson object at a given index into the database

Phone Book calls this member when it needs information about a person in the database. The member function is used in repainting the client window.

GetCount

Returns the size of the database

Phone Book calls this member when it needs to know how many people are in the database.

IsDirty

Returns TRUE if the database has unsaved changes

Phone Book calls this member when it must know if the database needs to be saved.

IsNamed

Returns TRUE if the database has been saved to a file (and thus been given the file's name)

Phone Book calls this member to learn if the database has already been saved or not.

GetName

Returns the current name of the database (and its file)

Phone Book calls this member to learn the name of the current database file for use in saving the file. The member contains a full path.

IsPresent

Returns TRUE if a CPersonList is currently allocated for the database

Phone Book calls this function to learn if an existing database is open or not.

GetTitle

Returns the title of the current database concatenated to the string “Phone Book -”.

Phone Book calls this function to build the window caption.

SetTitle

Sets the value of the database title

In Phone Book, this string is set using the information returned by CommDlg. CommDlg returns the filename and extension all in uppercase letters with no path.

File-Handling Member Functions

The file-handling member functions are familiar. They were used in the Data Model program in Chapter 2, where they were stand-alone functions called by main. Here, they have become member functions of the CDataBase class. These functions are as follows:

ReadDataBase

Deserializes CPerson data in a disk file into a new CPersonList object

WriteDataBase

Serializes the current database CPersonList to a disk file

Member Variables

A CDataBase object has several member variables. These store:

The current database, if any, as a CPersonList object in m_pDataList.

If no database is currently loaded, the CDataBase m_pDataList member variable is NULL, and the member function IsPresent returns FALSE.

The current found list, if any, as another CPersonList object in the CDataBase member m_pFindList.

This list stores pointers to any objects found by a call to the FindPerson member function of CPersonList. If no found list currently is present, the variable is NULL.

The current filename (and database name) in the m_szFileName member variable of class CDataBase.

If no file is currently open, this variable contains an empty string. If a database is open but has not yet been saved, the variable contains the string “Untitled”. Otherwise, it contains the filename with a full path.

The current file title in the m_szFileTitle member variable of class CDataBase.

This member variable is used to build a caption string for the window. If no file is currently open, this variable contains an empty string. If a database is open but has not yet been saved, the variable contains the string “Untitled”. Otherwise, it contains the filename in uppercase letters with no path. The filename is obtained from the standard Windows open file dialog.

The Phone Book application displays whichever of the two CPersonList objects is “current” according to this algorithm:

If there is a found list (the m_pFindList of the CDataBase member is not NULL), display it.

Otherwise, if there is a database (m_pDataList is not NULL), display it.

If neither list is present, no database is loaded, so display nothing.

Database display is managed by the main window object's OnPaint member function, which displays the current list. Because the mechanism for selecting the current list is encapsulated in CDataBase, OnPaint doesn't have to contain code to make the selection. Other inner workings of the database are handled similarly, which makes the Windows user interface code much simpler than it would be if it had to manage the details of the two lists.

The AssertValid Member Function

Programmers make many assumptions about the validity of their data. One of the more time-consuming tasks a programmer must perform is testing those assumptions at appropriate points to ensure valid processing. The Microsoft Foundation Class Library provides excellent support for this task.

Chapter 2 briefly outlined a mechanism for customizing validity testing of your own objects. During program development, you can use the ASSERT and ASSERT_VALID macros to test program assumptions. ASSERT simply evaluates its argument and, if the result is zero, prints a diagnostic message and halts the program. ASSERT_VALID calls the AssertValid member function of the object passed as its argument. Both macros are enabled in debug mode and disabled in release mode. In release mode, they do nothing.

To illustrate, suppose you have an object that represents a linked list with pointers to both the head and tail elements of the list. One reasonable assumption is that the list object itself exists. Another reasonable assumption is that if the list is empty its head and tail pointers should both be NULL.

Many of the member functions of class CDataBase invoke the ASSERT_VALID macro to test the validity of the list objects underlying the database object. ASSERT_VALID simply calls the specified object's AssertValid member function. Typically, the object you pass to ASSERT_VALID is the object pointed to by this, but it could be any object.

A class derived from CObject, as CDataBase is, can simply inherit CObject's version of AssertValid.

But a derived class can instead override AssertValid, as CDataBase does, to implement special testing when the ASSERT_VALID macro is invoked. If so, the overridden version is the one called through ASSERT_VALID. Hence, the response to the macro is customized.

CDataBase uses this mechanism to add validity tests for the objects stored in its data members to the validity test of the CDataBase object itself. The overriding AssertValid of CDataBase invokes the ASSERT_VALID macro again for its own m_pDataList and m_pFindList member variables, which hold CPersonList objects.

The chain of validity testing might stop at this level, but in this case class CObList, from which class CPersonList is derived, overrides AssertValid. This override performs additional validity testing on the internal state of the list. If the list is empty, its head pointer and its tail pointer must both be NULL. If not, an assertion is performed, diagnostic messages are printed, and the program halts. Similarly, if the list is not empty, both pointers must be non-NULL to avoid an assertion.

Thus a validity test on a CDataBase object leads to validity tests on the two stored CPersonList objects and then to additional validity tests for the internal states of those list objects.

This is quite a powerful mechanism when you build for debugging, and when you subsequently build for release, the mechanism is turned off automatically. The overriding AssertValid member function is not compiled in release mode, and the ASSERT_VALID macro invocations do nothing. You can put all of this to very good use, as demonstrated by CPersonList and CDataBase.

How CDataBase Is Used

The program's one CDataBase object stores two CPersonList objects, as mentioned earlier. It also stores the name of the current file. The program can create new databases and store them in a file, or it can open existing database files to permit operations on the data.

The program can have one of three states:

No database is currently loaded.

This is because the user has not yet created a new database with the New command in the File menu, has not opened an existing database with the Open command in the File menu, or has closed a previously open database. There is no database to manipulate. The user can create or open a database or exit the program.

In this case, the database object's m_pDataList and m_pFindList member variables are NULL. The m_szFileName member variable contains an empty string.

A database exists, but it hasn't been saved to a file.

The user has chosen the New command in the File menu, which creates a new, empty database. Because the database is unsaved, the string “Untitled” appears in the title bar of the window. The user can add persons to the database, delete persons from it, edit the data for persons already in it, search the database for a particular last name, get information about the program, including help, save the current database to a file, open or create a new database (and be prompted to save the existing one first), or simply exit the program.

In this case, the CDataBase member variable m_pDataList contains a
CPersonList object. If the data has been searched, and the search results not yet deleted, m_pFindList contains a second CPersonList object. The m_szFileName member contains the string “Untitled”.

A database exists, and it has been saved to a file.

The window caption has been set to the filename. If the user has added, deleted, or edited since the last save, the database is flagged as having unsaved changes. The user can manipulate the database further, close it, open a new database (and be prompted to save the old one if there are unsaved changes), or exit the program.

In this case, the CDataBase object's m_pDataList member variable is not NULL. The database object's m_pFindList member is NULL if no search
is in progress but contains a CPersonList object if the user has chosen the
Find command in the Person menu. The database object's m_szFileName
member contains the name of the file the data was saved to.