Data Modeling (w/ structures)
Oftentimes when designing programs, we tend to think first about the tasks (or procedures) necessary to solve a problem and second on the data (variables) necessary to store information. In other words, we design our programs by thinking about procedures first, data second. This can be termed a procedural method of program design. Consider, for example, designing a bowling simulator program. A first inclination might be to think about the functions necessary to implement the scoring system rather than on say the balls, pins, and the other “data” necessary for the program. Or consider writing a program to simulate a clock. A procedural method of design would first focus on the functions (and how they work together) to keep the time rather than on the data to store the time. In procedural design, the focus is primarily on the end application and the functions necessary to solve the particular problem at hand.
An alternative method of program design is to think about the data first, procedures second. Rather than first asking how, we ask what. What are we working with in the problem description? What is the data involved in the problem description? In other words, we concern ourselves first with the data (or objects) and second with the procedures. In this way, we orient our design toward the data (objects) in the problem. In this way, our design becomes data-centered or object-oriented. Although the design order of procedures and data might seem like a minor point, it can make a major impact in the overall usefulness of program code. Most importantly, it can affect how reusable code can be for other applications. In this course, one of our goals is effective object-oriented program design. Among other advantages, object-oriented design methods can result in code that can be more effectively reused in other applications – an important factor in efficient software development. By properly designing objects, we develop code modules than can not only be used to solve the problem at hand, but also mixed and matched with others to form new applications. Similar to Lego™ pieces (see figure below), object-oriented design creates individual, reusable code modules.
Designed properly, these code modules can be mixed with other code modules to solve not only the problem at hand, but other applications. In this way, the focus becomes more on developing effective pieces rather than on the end application. With enough proper pieces, we can build almost any kind of application (see figure below).
Object-oriented program design begins with an analysis of the data models in a problem description. Data models represent the objects, data, or nouns in a problem description. For each main object identified in a problem description, we ask two basic questions, “what does the object know?” and “what does the object do?” Consider the problem description of writing a clock simulation program. The primary object or noun in such a description is the clock. We can use a two-column table below to develop the data model for a clock. Under each column we list information to answer our data model questions.
What does it know?
What does it do?
Analog or Digital
Our next step is to realize this data model by translating this information into actual programming code. By carefully structuring our code, we can implement our data model as a reusable module that can be used to solve not only our original problem but any problem requiring a clock. Using this object-oriented design method, we first develop data models then build applications using these data models. In this way, we can start to build up a collection of reusable objects that can be used in a variety of applications.
To implement data models we could use any programming language. Design methods are independent of programming languages. Although some languages (Java, C++) are more conducive to object-oriented design, we can structure code in any language to reflect this design methodology.
Let’s compare the two design methods discussed above for a simple problem description.
A 30% salt solution is prepared by mixing a 20% salt solution and a 45% solution. How many liters of each must be used to produce 60 liters of the 30% salt solution?
To solve such a problem, we can use two equations with two unknowns. The first equation describes the volume of the solutions (where xvol,yvol,fvol are the total volumes to be mixed and the final volume). The second equation describes the volume of salt (where xper,yper,fper are the salt volumes to be mixed and the final volume).
Equation 1: xvol + yvol = fvol
Equation 2: (xper*xvol) + (yper*yvol) = (fper*fvol)
Using substitution to solve for the unknowns, we can derive the following equations to find the final amounts of the two solutions:
yvol = ( fper*fvol – xper*fvol ) / ( yper-xper )
xvol = fvol – yvol;
A typical top-down, procedural solution might look something like the following. We’ll use the C/C++ programming language to implement the application. Without thought about the data or objects in the problem, we simply start with our main function and work to solve the problem at hand. Note that although this design solves the problem, we really can’t reuse any of the code for other applications.
using namespace std;
float xvol = 0.0;
float yvol = 0.0;
float fvol = 60.0;
float xper = 0.2;
float yper = 0.45;
float fper = 0.30;
yvol = ( fper*fvol - xper*fvol ) / ( yper - xper );
xvol = fvol - yvol;
cout << "Solution x:" << endl;
cout << " volume: " << xvol << endl;
cout << " percentage: " << xper << endl;
cout << "Solution y:" << endl;
cout << " volume: " << yvol << endl;
cout << " percentage: " << yper << endl;
cout << "Solution f:" << endl;
cout << " volume: " << fvol << endl;
cout << " percentage: " << fper << endl;
Object-oriented program design begins with an analysis of the data models in the problem description. Analyzing the problem we note that the main object in the description is the noun solution. Building our data model table we ask the questions, “what does a solution know?” and “what does a solution do?”
We can use structures to neatly store the information a solution knows about as follows:
// Stores what a solution knows
float vol; // volume
float per; // percentage
We can use functions and pointers to structures to implement what a solution does:
// Initialize a solution
void InitSolution( Solution *m, float vol, float per )
m->vol = vol;
m->per = per;
// Mix a solution
void MixSolutions( Solution *x, Solution *y, Solution *f )
y->vol = ( f->per*f->vol - x->per*f->vol ) / ( y->per - x->per );
x->vol = f->vol - y->vol;
// Print information about a solution
void PrintSolution( Solution *m )
cout << "Solution:" << endl;
cout << " volume: " << m->vol << endl;
cout << " percentage: " << m ->per << endl;
We’ve implemented our data model for a solution using a structure (storing what it knows) with functions (describing what it does). Note that our functions each operate on a single data model object (a solution). We accomplish this by sending in the address of the object to operate on as the first function argument. We pass by reference in this manner so our operations can modify the object if necessary. We try to restrict our functions to operate on a single object so we could maximize our reuse of this function. By organizing code for the data model separately, we could reuse the structure and functions for any application requiring a solution. In this manner, we create a distinction between the data model and the application. An application addressing our original program statement would look like the following.
// Declare 3 solutions
// Initialize solutions
InitSolution( &x, 0.0, 0.20 );
InitSolution( &y, 0.0, 0.45 );
InitSolution( &f, 60.0, 0.30 );
// Mix solutions
MixSolutions( &x, &y, &f );
// Print solutions
PrintSolution( &x );
PrintSolution( &y );
PrintSolution( &f );