If you purchase a class library then the classes within the library are already built for you. If you want to build a class library yourself or extend a class library that you have purchased then proper design of the new classes that you are going to create is important so as to make the class as user friendly and reusable as you can make it. This page deals not so much with how to define your new classes but rather concentrates more on what needs to be defined in order to make classes that can be reused in future programs that you may write. Properly designed and implemented reusable classes will reduce the work required on subsequent projects that can use these classes particularly where the classes can be used without needing any alterations or additional coding.
Another option that may be available to you if your new classes are sufficiently useful and well designed is that you may even be able to sell the resultant library to other developers as a separate product rather than just using it within your own projects.
The first step in defining your own classes is to determine what classes you need to create. In order to do this you need to do a proper analysis of your requirements and identify what classes are required to meet those requirements. In doing so you need to keep in mind the elements of an object oriented system. Unified Modelling Language (UML) has been developed as a useful method of documenting the class definitions resulting from your analysis.
You will need to determine the classes that you need depending on what it is that you are trying to do and the way that you determine is the most meaningful way of processing the necessary information. As a real life example of the sort of class list that you might develop, the following is a list of classes that I developed to allow text based reports to be easily formatted for the screen or printer. This list of classes was not developed all in one go but rather via a hierarchical analysis of the ways in which a generic report could be broken up into its component parts. The classes at one level within this list are actually derived from or their definition includes classes from the lower levels within the hierarchy. The list actually starts at the lowest level in the hierarchy and works its way up but in developing your list of classes you will probably start at the top and work down.
You will notice a number of relationships between the classes I defined in this list. ReportBlock is a linked list of PrintLines and ReportFieldList is a linked list of ReportObjects. ReportString, ReportDate, and ReportNumber are all derived from ReportObject to provide specific instances of object types that can be added to the report. ReportDefinition provides the link between ReportFieldList and the various ReportBlocks in order to complete the definition of a generic report. Finally, ReportPrint handles the actual loading of the data into the report based on the ReportDefinition.
The actual use of these classes to create a report would involve defining a ReportDefinition; adding ReportString, ReportDate, and ReportNumbers to the ReportDefinition so as to define the format of the report. This would then be followed by defining a ReportPrint based on the ReportDefinition and using it to process the data to be printed on the report. The other classes within this hierarchy will only be used within the report production process itself and will not need to be accessed other than from the classes that are listed above.
When you have determined the classes that you require for your class library, you will probably find that the classes have similar relationships to those I have illustrated here. In this instance the further definition of the higher level classes becomes dependent on having first fully defined the public interface of the classes at the lower levels.
The next step after determining what classes you are going to define is to determine how each class will be accessed from outside of the class. To do this we need to define the Public Interface for the class. In defining the public interface, wee need to look not only at those functions that we currently require the class to support but also any other functions that a future project may require the class to perform. By including as many of the possible functions into the original definition of the class, we improve the chances that the class will be reusable for subsequent projects. In fact you may find it easier to not consider the requirements of the current project when defining the public interface for the class but rather think in more general terms of the things that you would expect such a class to be able to handle.
To continue using my example of a generic formatted text report generator, let's take a closer look at the PrintLine class. The main functions that this class needs to perform are:
These provide the basic functions associated with the PrintLine class and identify those things that we expect to be able to do with a print line ie. add fields to the print line, retrieve a complete line for subsequent processing, test if the line is empty, and empty the line ready to reuse it.
As we will be defining ReportBlock as a linked list of Printlines, we also define ReportBlock to be a friend of this class and define Next and Previous functions and a few other functions to allow specific lines within the list to be located as well as add and subtract functions to allow the line to be added to and removed from a list.
As you can see, the Public Interface for the class relates rather specifically to the functions that you expect the class to be able to perform.
Having completed the definition of the public interface for one class, we can now move on and define the public interfaces for the other classes. Once all of the public interfaces have been defined the full functionality of the classes will be available to be used by other classes and those other classes can (if necessary) be built although at this point the classes in our new library do not as yet perform the functions that they claim to do.
Once the Public Interface has been defined then all that is left is to fill in the actual processing required to perform the functionality defined in that public interface.
In some cases the functions available through the public interface can also to be used within the processing of other functions (for example the clearLine function of the ReportLine class can be called from within the class constructor to initialize the content of the print line).
At other times as you proceed with coding the actual processing, you will find a need to provide additional functions internal to the class as well as fields to store the information associated with the class. The definition of these items do not form part of the public interface for the class but rather should be defined as private to the class. In this way we eliminate the possibility of these fields and functions being accessed directly rather than via the public interface. If the only accesses to the class can be done via the public interface then any changes to the processing within the class can be made based on that knowledge and the only time the changes need affect anything using the class is where changes to the public interface itself is required.
Once the functionality is complete, a test harness should then be built enabling the functionality of all of the functions within the public interface to be thoroughly tested using a wide range of values for which the expected results are known. Particular attention should be paid to any known boundary conditions that the functionality has been set up to handle with special processing to ensure that this processing works correctly.
Provided that sufficient thought and planning goes into determining what classes to create and what to put in their public interface, you should end up with useful classes that you will be able to use not only for your current project but also in many future projects. Reuse of classes like this should over time reduce the coding required for each project making it easier and faster for each project to be completed. By thoroughly testing all of the functionality of a class when it is first created and all of the internal processing well known, the testing of subsequent projects that use these classes can be preformed in the knowledge that the class is performing correctly based on the information provided to it and that any unexpected results in testing relate to the code in the routines where the class is used and not within the class code itself.
Oh, and while the creation and testing of the generic formatted text report class library occupied much of my time over several weeks, the creation and testing of six specific reports (each with very different requirements from the others) was subsequently completed in about half an hour with each report requiring barely enough code to define what the report should contain. The next time that I need to define reports, the process should be equally quick unless one of the new reports requires functionality that I overlooked in building my class library. Should this be the case, the additional functionality will hopefully be able to be added through provision of additional public functions that can be added to the existing classes or by changing the processing performed by those functions rather than requiring changes to the existing public interface.
This article written by Stephen Chapman, Felgall Pty Ltd.