- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
Testing the Date Class 159
Now, this is a lot of code to deal with. Not to worry — the code breaks down into three separate pieces:
Initialization code (shown at |
1) either |
|||
sets or gets our individual member |
variables |
|||
and initializes them to reasonable defaults. |
||||
Validation code (shown at |
2) checks to see |
|||
whether or not the input data is reasonable, |
||||
given the rules and the current settings. |
|
|||
Algorithmic code (shown at |
3 and |
4) |
||
does the actual date manipulation |
and |
|
calculations.
2. Save the source-code file and close the code editor.
Always break your classes into discrete initialization, validation, and calculation pieces. This saves you time by focusing your efforts on what needs to be done, rather than worrying about how to do it.
3. Compile the test code to make sure that you have all of the code properly entered and correct.
Testing the Date Class
As with any other utility class, after you have the code written for the class, you must be able to provide a test driver for that class. The following steps show you how to create a test driver that illustrates that the code is working properly — and shows other programmers how to use the class in their own applications.
1. In the code editor of your choice, create a new file to hold the code for the test driver.
In this example, the file is named ch27.cpp, although you can use whatever you choose.
2. Type the code from Listing 27-4 into your file.
Better yet, copy the code from the source file on this book’s companion Web site. Change the names of the constants and variables as you choose.
LISTING 27-4: THE DATE TEST DRIVER CODE.
#include <stdio.h> #include “date.h”
void DumpDate( Date& d )
{
printf(“Date:\n”);
printf(“As String: %s\n”, d.AsString() ); printf(“Month: %d\n”, d.Month() ); printf(“Day : %d\n”, d.DayOfMonth() ); printf(“Day of Week: %d\n”, d.DayOfWeek() ); printf(“Year: %d\n”, d.Year() );
printf(“Leap Year: %s\n”, d.isLeapYear() ? “Yes” : “No” ); printf(“Number of days in this month: %d\n”, d.numDaysInMonth() );
}
int main()
{
// Initialized date to no values. Date d1;
(continued)
160 Technique 27: Building a Date Class
LISTING 27-4 (continued)
//Initialize to the end of the year to test edge cases. Date d2(12,31,2004);
//Print out the dates as strings for testing. printf(“D1 as string: %s\n”, d1.AsString() ); printf(“D2 as string: %s\n”, d2.AsString() );
//Test year wrap and the operator +=.
d2 += 1; |
6 |
printf(“D2 as string: %s\n”, d2.AsString() ); |
//Test backward year wrap and the operator -=. d2 -= 1;
printf(“D2 as string: %s\n”, d2.AsString() );
//Test the assignment operator.
Date d3 = d2;
//Check to see whether the class works properly for
//assigned objects.
d3 -= 10;
printf(“D3 as string: %s\n”, d3.AsString() );
//Validate the day of the week. Date d4 (7,27,2004);
printf(“D4, day of week = %d\n”, d4.DayOfWeek() );
//Test the pieces of the date.
Date d5;
d5.setMonth( 11 ); d5.setDayOfMonth( 31 ); d5.setYear( 2004 ); d5.setFormat( YYYYMMDD );
DumpDate( d5 );
return 0;
}
3. Save the code as a file in your editor and close the code editor.
4. Compile and run the application.
If you have done everything properly and the code is working correctly, you should see output that looks like this:
$ ./a.exe
D1 as string: 01/01/2004
D2 as string: 12/31/2004
D2 as string: 01/01/2005
D2 as string: 12/31/2004
D3 as string: 12/21/2004 D4, day of week = 3 Date:
As String: 2004/12/31
5
7
Some Final Thoughts on the Date Class |
161 |
Month: |
12 |
Day : |
31 |
Day of |
Week: 0 |
Year: |
2004 |
Leap Year: Yes |
|
Number |
of days in this month: 31 |
There are some important things to take away from
this output. First, look at the line marked |
|
5 in the |
date object |
||
output listing. This line is output for the |
|
|
which is defined with the void constructor. As you can see, the object is properly initialized with a valid date. Next, let’s look at the line marked with 6. This line is output after we added one day to the 12/31/2004 date. Obviously, this forces the date to wrap to the next year, which we can verify by looking at the output, showing 01/01/2005. We can also
verify, by looking at a calendar, that the date shown at 7 really does fall on a Tuesday (the 3 in the
output). Finally, we run some simple tests to verify that the number of days in the month is correct for December, that the pieces of the date are parsed properly, and that the leap year calculation is correct.
All of this output data allows us to validate that our class works properly and that the functionality can easily be moved from project to project. This will save us a lot of time, and allow us to design our programs with the date functionality already built.
When you are testing a class, make sure that you exercise all of the functionality in the ways your class is most likely to be used — not just the ways that make sense to you at the time. Our tests verified that the date math, formatting, and accessor methods all worked properly.
Some Final Thoughts on the Date Class
As you can see, our Date class is really very useful. However, it could easily be made more useful. For example, you could allow the user to pass in a string to be parsed into its date components, thus solving a common programming problem. Another possible enhancement would be to initialize the default constructor to be the current date. Finally, it would be nice to have the date strings, such as the month and day names, within the class itself and accessible. This would protect them from access by programmers from outside the class. In addition, it could allow us to read them from a file, or get them from some internal resource, to provide internationalization without forcing the end user to know where the data is stored.
If you store literal string information in a class, make sure that the programmer can replace it from outside the class. This will allow the developers to put in their own descriptions, change the text for internationalization, or just modify the text to fit their needs.
28 |
Overriding |
|
|
|
Functionality with |
Technique |
Virtual Methods |
|
Save Time By
Using factory patterns
Building a manager class
Testing the manager class
One of the most common “patterns” of software development is the factory pattern. It’s an approach to developing software that works like a factory: You create objects from a single model of a particu-
lar object type, and the model defines what the objects can do. Generally, the way this works is that you create a factory class that allocates, deallocates, and keeps track of a certain base class of objects. This factory class really only understands how to manage the object type that forms a base for all other objects in the class tree. However, through the magic of virtual methods, it is able to manage all of the objects. Let’s take a look at how this works. By creating a single factory, using virtual methods that processes a variety of types of objects, we will save time by not having to reimplement this processing each time we need it.
First, we have a class that manages a given base class of objects — it’s called a factory. Its uses virtual methods to manage objects — that is, to add new objects, remove them, return them to the user, and report on which ones are in use and not in use.
Next, we have a set of derived classes. These override the functionality of the base class by using virtual methods to accomplish different tasks. As an example, consider the idea of a variety of different kinds of classes to read various types of files. We would have a base class, which might be called a
FileProcessor class. Our manager would be a FileProcessorManager class. The manager would create various FileProcessors, based on the file type that was needed, creating them if necessary or returning one that was not currently in use.
When you implement a common base class, set up an object pool to manage the objects based on it. That way you can always keep track easily of how they are created and destroyed.