Moreover, to make the class diagram more manageable, the developers often omit the operation section from the class notation and discuss relationships using class diagrams, where classes
Trang 1both functions perform similar operations The objects of the operation are somewhat different, but their general semantics (meaning) is the same It is only natural that their interfaces be the same.
C++ rules are silent on that It is okay to overwrite a base function with the same interface but this
is not mandatory For whatever reason, many C++ programmers believe that the interface must be the same This is not the case Whether the designer of the derived class changes the method
interface or keeps it the same as in the base class, the base method name is hidden from the derived class client code It is the derived class method that is called from the client code The client code can use the base method if desired, but this requires the base class scope operator to give a
command to the compiler and a visual cue to the human reader
Cylinder cyl1(2.5,6.0), cyl2(5.0,7.5); // initialize data double length = cyl1.getLength(); // similar to Circle cyl1.set(3.0);
double diam = 2 * cyl1.getRadius();double vol = cyl2.getVolume();
// not in Circle
cout << " Circumference of first cylinder: " << length << endl;
cout << " Volume of the second cylinder: " << vol << endl;
cout << " Diameter of the first cylinder: " << diam << endl;
cout << " Side area of first cylinder: "
<< cyl1.Circle::getArea() << endl; // visual cue
Pluses and Minuses of Inheritance and Composition
Inheritance is a good abstraction tool It explicitly stresses conceptual connections among classes when these connections exist For example, the commonality between classes Circle and
Cylinder is best reflected in the program design by deriving class Cylinder from class Circle.
This inheritance relationship is conspicuous in the Cylinder code For the client programmer and for the maintainer, there is no need to study the classes separately, comparing the code of one class against that of another class, trying to figure out whether these classes are similar
The use of inheritance helps us to save development effort It does not always make the source code for class implementation shorter Often, it is the other way around Still, many programmers believe that it is easier to develop a complex class in simple stages rather than as a monolithic unit For example, developing the class Circle first allows the designer to concentrate on relatively simple things (like computing circumference) and tackle more complex tasks (like computing the cylinder volume or area) later, when class Circle is firmly in place
However, the use of inheritance introduces extra implicit dependencies between classes, which might not be obvious to the designer (or maintainer) of the client code Studying the derived class
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 2description does not provide the reader with the list of services available to the client The reader has to study the base class as well.
The use of class composition is good competition for inheritance The aggregate class provides the reader with the complete list of services that the class supports Composition also introduces
dependencies between classes, but these dependencies are explicit, in the form of one-liner
methods, which push the work from the container class to the methods of the component class
The choice between inheritance and composition depends on the degree of similarity between the related classes If the number of common methods is relatively small and the number of additional services to be supported is relatively large, composition is the way to go The aggregate class will have only a few one-liner functions, and the expense of writing them will be offset by the
availability of the explicit list of supported services
If the number of common methods is relatively large and the number of additional services is
relatively small, inheritance might be the way to go Many programmers are annoyed at the need to write "dumb" one-liner methods The use of inheritance will eliminate these one-liner
methods¡Xthe base class methods will be inherited by the derived class directly Redefining base methods in the derived class is aesthetically pleasing and opens the way to the use of polymorphism (to be discussed in the next chapter)
If you use inheritance, use it in the simplest way possible, as public derivation Avoid protected and private inheritance
Often, inheritance is used just to speed up work, without a clear conceptual "is a" connection
among classes If you have doubts that a natural "is a" relation exists among classes, do not use inheritance; use composition
Unified Modeling Language
Traditional programs are written as systems of cooperating functions Object-oriented programs are written as systems of cooperating classes that include both data and functions In the first part of this book, I concentrated mainly on techniques of writing functions The skills of writing
processing code and implementing communications between functions are crucial for creating quality C++ programs
high-In the second part of the book, I concentrated on writing classes The skills of binding together data and operations and implementing classes as servers and as clients are crucial for creating high-
Trang 3inheritance and composition are¡Xwhat else?¡Xcrucial for creating high-quality C++ programs.
When a C++ program becomes sufficiently complex, it becomes difficult to visualize the
relationships between classes implemented by the program Moreover, when a C++ program
becomes sufficiently complex, it becomes difficult to visualize the relationships between classes you are going to implement
The Goals of Using UML
A common approach to this problem is to use graphical notation to describe the relationships
among real-life entities whose behavior the application should emulate¡Xcircles and cylinders, customers and accounts, inventory items and their suppliers This is the task of object-oriented analysis, which describes the system activities in the form of cooperation among classes rather than
in the form of cooperation among operations (functions)
The next step is to decide which real-life entities should be represented as classes in the C++
program and which relationships between real-life entities should be implemented as relationships among classes in the C++ program This is the task of object-oriented design, which also uses
graphical notation to describe the classes and their relationships within the program
Unlike object-oriented systems analysis, which concentrates on describing program external
interfaces (with users and other systems), object-oriented design concentrates on describing the structural elements of the system: system architecture, allocation of its subsystems to different hardware components, and links between different classes Most of these links are the same as the links between entities established at the object-oriented analysis phase, but some analysis links might not be implemented in the program, and some additional links between classes (and some additional classes) might be added to improve system performance or user interface
In the first two parts of the book, I discussed methods of making system components less
dependent on each other This is a very sound approach because dependencies between system components require cooperation between developers, and cooperation between developers is prone
to error But it is not realistic to expect that system components are totally independent from each other Since they are part of the same system, they have to cooperate with other components It is important to keep this cooperation to a minimum, but it is also important to describe this
cooperation with the appropriate degree of precision
Enter the UML notation In the traditional system development process, the analysis, design, and implementation phases all use different techniques of graphical notation to support the diverse interests of the analyst, the designer, and the programmer In the object-oriented development
process, the analyst, the designer, and the programmer use the same notation This has two
important advantages over the traditional approach First, the cooperating developers¡Xanalysts,
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 4designers, and programmers¡Xuse the same graphical notation and hence there is less opportunity for misunderstanding or for different interpretations of implicit assumption Second, there is no drastic change of representation between the phases of the development process and hence there is less opportunity for errors creeping in during the course of transformation.
UML is a powerful modeling language that allows the developer to use graphical notation for
representing relationships between cooperating system components "Cooperating" means that the system components know about each other, use each other, and depend on each other This
graphical notation is based on the concept of an object as a system component, with its data
members, member functions, and relationships among objects These object diagrams can be
discussed with system users and system implementers to verify that the relationships are reflected correctly Later, these diagrams are converted to object-oriented implementations
This is a significant conceptual leap from the object-oriented programming approach I was
describing earlier The essence of object-oriented programming is binding together data and
operations It is the combination of specific data members and the operations over them that
characterizes a C++ class This definition says nothing about relationships This is unfortunate because it creates a false impression that the combination of data and behavior describes the object sufficiently well
Object-oriented analysis and object-oriented design take a different approach They describe
objects as a combination of data, behavior, and relationships with other program components As you will see, the description of data and behavior plays a rather basic role in UML It is the
relationships between classes and objects that most UML notation concentrates on
The object-oriented programming approach underplays the significance of relationships, or
associations, because programmers try to make the program components as independent from each other as possible As a result of this concentration on component independence, all object-oriented languages give the programmer the specific means for describing data members and for describing member functions They do not give the programmer the native means for describing associations among program components
Real life being what it is, program components are always somehow related to each other Hence, programmers face the task of describing these relationships using ad hoc techniques: data members
of programmer-defined types or data members that are pointers or references to other objects
The major role of every design notation, including UML, is to help developers describe the
relationships among objects When a C++ program is being implemented, it is up to programmers
to decide which objects should be related to which objects It is nice to be able to implement the program so that it can do whatever it is supposed to do without becoming excessively complex Using the UML notation, the developers can compare different design decisions and choose those relationships that (1) are sufficient for doing the job and (2) minimize the complexity of
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 5relationships among program objects.
UML merges three different systems of notation for building graphical models of computer
systems These models help the developers to analyze the requirements for the system The
requirements usually describe system functionality, user interface, interfaces with other systems, performance, and reliability The graphical methods try to represent these requirements in the form
of relationships among system components This is, of course, an ambitious undertaking
One system of notation was developed by Grady Booch and his company, Rational Software
Corporation His notation included several views of the system with a separate diagram for each view The symbols for objects on Booch diagrams were of irregular "cloud" form (very different from the client-server diagrams I used in previous chapters) and were hard to draw by hand Booch was one of the first to realize that graphical modeling needs the support of computer-based tools The tool that his company developed, Rational Rose, is one of the most successful tools for object-oriented modeling and is largely responsible for the popularity of the Booch approach With the introduction of UML, Rational Rose was modified to support the UML notation as well
Another system of notation was developed by James Rumbaugh and his associates at General
Electric It was called the Object Modeling Technique (OMT) In addition to the object model, which described the relationships among the objects in the system, the OMT notation also included two other models, the dynamic model and the functional model Even though these two models were not particularly object oriented, they represented the adaptation of two well-known design and analysis notational techniques: state transition diagrams and data flow diagrams This synthesis smoothed the transition to the object-oriented approach for those developers who had experience in these two graphical notations This is probably the major reason that OMT was gaining popularity
in industry as an emerging standard approach
The third system was developed in Sweden by Ivar Jacobson and his company, Objective Systems
He marketed his notation under the names Object-Oriented Software Engineering (OOSE) and Objectory This notation included so-called "use cases" that describe the interactions between the system and external actors such as the system operator or an interface with other systems
Each system of notation was described with recommendations on how to use the notation for first, object-oriented analysis, then for object-oriented design, and finally, for object-oriented
implementation Each book tried to explain why the object-oriented approach was better than the traditional approach I have to be frank: these explanations were not particularly clear They
worked well for people who already wanted to believe in the advantages of the object-oriented approach Those who did not know where the savings and improvements in quality would come from remained unimpressed
However, the drawbacks of the traditional approach to system development were so serious that the
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 6industry was willing to accept anything that would promise an improvement over the existing state
of affairs But what approach to accept? In addition to the systems of notation developed by Booch, Rumbaugh, and Jacobson, there were similar systems of notation described by Shlaer and Mellor, Yourdon and Coad, Coleman, and others¡Xactually, it is quite a long list All of these systems of notation are similar, and all are a variation or expansion of the work on entity-relationship diagrams for data base design performed by P Chen in 1976
For a number of years, the authors of the different systems of notation devoted significant energy to public argument: which system of notation was better and why The idea behind this argument was that the choice of method was a very important decision that had to be approached with proper care Some experts believed that one approach might be better than the others for one kind of software system (e.g., real-time systems), while another approach might be better for another kind of
software system (e.g., business systems) Other experts were saying that one method was better than all the others¡Xperiod These arguments were called "the method war," although the
differences between competing approaches were in notation rather than in methodology; and the differences in notation were not really significant
Around 1995, Booch, Rumbaugh, and Jacobson, the "three amigos," as they were called, decided to create a unified system of notation that would become the dominant modeling language (another way to describe this is to say that Booch hired both Rumbaugh and Jacobson to work for Rational) The UML is the result of their collaboration They wrote books that describe the UML and the
ways to use it The Rational Rose tool provides full support to the UML notation
The bad news is that the UML notation combines diverse ideas and hence is complex The books that describe it are unwieldy The learning curve for mastering the modeling language is
steep¡Xthere is so much to learn However, when a designer makes a bad modeling decision, there
is no compiler to flag the error (unlike the C+ compiler that helps you learn when you make an error) This is why the process of mastering the UML is much slower than, for example, the process
of learning C++
The good news is that you do not have to be an expert in UML to be able to communicate your ideas about the structure of object relationships in your program In this book, I am describing only the basics of the UML that are sufficient for discussing object relationships in C++ programs
Basic UML: Notation for Classes
Objects in UML are considered to be instances of classes, and classes are descriptions of object types The class describes the attributes and behavior of one type of object The major source of classes that you want to include in your UML model is the analysis of the concepts and entities of the problem domain: the area of the application For a business system, for example, the classes in the model would be Customer, Item, Shipment, Requisition, Invoice, and so on For real-time
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 7application, the classes would include Sensor, Display, CustomerCard, Button, Motor, and Lock.
Classes included in the model are placed in a class diagram A class is represented as a rectangle divided into three sections or compartments The top section contains the class name, the middle section contains class attributes, and the bottom section lists operations When you implement the class in C++, the attributes become data members, or fields, and the operations become member functions, or methods Figure 14-3(a) shows a general picture of a class in UML Figure 14-3(b)
shows a specific example of the class Point with attributes x and y, operations set(), get(),
and move(), and the assignment operator Figure 14-3(c) shows an example of class Rectangle
with the attributes thickness, pt1, and pt2, and operations move() and pointIn().
Figure 14-3 UML examples of a generic class template and two specific classes.
You see that UML allows you to indicate the type of attributes as either primitive (built-in) types or
as a library class (e.g., String) or as one of the classes defined in the application (e.g., Point) UML allows you to specify much more than just the name and the type of the attribute You can indicate whether an attribute is static (class scope attribute), the set of allowed values (if the
attribute is the enumeration type), the attribute initial value (if it has any), or even the attribute visibility (public, private, or protected) This is optional, because often, especially at the beginning
of the analysis and design process, the developers are not sure what the types and other properties
of the attributes are These properties might be clarified later, during the iterative process of
refining the design or even during programming
For operations, UML allows you to specify the operation signature: its name, return type, and the names and types of the parameters You can also specify the default parameter values (if they are needed), whether the operation is a static operation (class scope operation), and the operation
visibility (public, private, or protected)
As you can see, the UML class description can have as many details as does the class specification written in C++ I will spare you the specific details of the UML notation for attributes and
operations because it is not necessary for our discussion of relationships among classes Moreover,
to make the class diagram more manageable, the developers often omit the operation section from the class notation and discuss relationships using class diagrams, where classes have only two
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 8sections, for the name of the class and for the attributes For even more complex diagrams (and most class diagrams are complex), you can omit the attribute section as well and represent the class
as a rectangle with the class name only This is the most convenient way of discussing the class relationships
Basic UML: Notation for Relationships
Real-life entities in the application domain might be related to each other These relationships are represented on the class diagram using connections between classes The technical term for a
connection between classes is the association The existence of an association between classes means that objects of these two classes have a link between them This link might mean that one object knows about another object, or is connected to another, or uses another object to achieve its purposes, or even that for each object of one class there is an object of another class This is very general and very important: The associations, when implemented, are used to access one object through another object in the C++ program
Figure 14-4 shows examples of associations Figure 14-4(a) indicates that each Circle object is associated with a Cylinder object but does not specify the nature of the association Notice that it
is only the name of the class that is represented in the class rectangle; the attributes and operations are not there They can be put there, too, but that would only clutter the class diagram; which is worth doing only if the list of attributes and operations somehow clarifies the nature of the
relationship between objects
Figure 14-4 UML examples of associations among classes.
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 9The association between objects is usually bidirectional¡Xif a Circle object is associated with a
Cylinder object, then the Cylinder object is related to the Circle object In the case of these two objects, there is little that can be said about the relationship between the objects The UML notation allows us to express additional information about the relationship by inventing names for the
associations and by assigning roles to the objects associated with each other
Figure 14-4(b) shows that a Person object can be related to a Car object, and a Car object can be related to a Person object and a Registration object Each association has two labels, one for traversing the association in one direction and another for traversing the association in the opposite direction To avoid confusion as to the direction in which the name connects the objects, you can put small arrows next to the name
In Figure 14-4(a), I was not able to come up with a good name for the association between Circle
and Cylinder objects They are related, and that is it In Figure 14-4(b), I am saying that a Person
object owns a Car object, and a Car object is owned by a Person object In addition, I am saying that a Car object is registered by a Registration object, and a Registration object registers a
Car object I also specify the role of each object in the relationship A Person object plays the role
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 10of an owner, a Car object plays the role of a vehicle, and a Registration object plays the role of a document.
This probably does not strike you as a very profound method for describing relationships among objects Rest assured that you are not alone Indeed, just knowing the names of links between
objects and the roles that the objects play does not help much toward understanding how objects cooperate in real life and how program objects cooperate during program execution If, however, the developer knows very little about the application domain, the names of associations and the roles of objects might be helpful in channeling the analysis in the proper direction
Often, comparing different names for relationships in a class model feels like describing the theory
of relativity in simple terms The major problem with describing associations is that any solution is indeed relative Figure 14-4(c) describes the associations between classes Person, Car, and
Registration using different relationships The third alternative is to associate each class with two other classes Which alternative is better and why? There is no good answer to this question
Association can be implemented in C++ with pointers (or references) that point from one object to another (associated) object Another popular technique of implementing association in C++ is the use of an object identifier as an attribute of another class For example, class Person might have an attribute that identifies a Car object associated with the Person instance If necessary, class Car
might include a Person identifier as an attribute that associates the Car instance and the Person
instance
Basic UML: Notation for Aggregation and Generalization
Aggregation is a special case of association It indicates that two classes are connected through an association, but the association is special This association indicates that the relationship has the
"whole-part" meaning¡Xone object is part of another object, or another object contains the first object (has it) or consists of some objects
The UML notation for aggregation is the same as for association: a link between classes To
indicate the aggregate object, a hollow diamond is attached to the end of the line between the link and the aggregate object Obviously, the diamond can be attached to only one end, not to both
Figure 14-5(a) demonstrates that a Circle object is part of a Cylinder object Actually, a hollow diamond indicates that the aggregation is shared, and the part may be in more than one aggregate at the same time In composition aggregation, sharing is not allowed The UML notation for the
composition aggregation is the same as for the shared aggregation, but the diamond attached to the aggregate is solid rather than hollow Figure 14-5(b) demonstrates the notation for composition aggregation, where a Circle object is part of a Cylinder object but it cannot be part of any other object
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 11Figure 14-5 UML examples of shared aggregation, composition aggregation, and
inheritance.
Since aggregation is a special case of association, it is always possible to represent the relationship between objects as association rather than as aggregation (shared or composition) See, for
example, Figure 14-4(a), where association is used to model the relationship among Circle and
Cylinder objects However, this model is less precise It is the task of the designer to represent aggregation as aggregation rather than as just association This will result in a simpler
implementation Of course, if aggregation does not represent the relationship among objects well, association should be used The struggle between arguments in favor of general association and specific aggregation is often the source of anguish for the designer
The shared aggregation can be implemented in C++ similar to an association, with pointers (or references) to the component objects The composition aggregation can be implemented using the part objects as data members of aggregate objects
Generalization is the relationship between a more general class and a more specific class The more specific class contains the same attributes and operations as the more general class and might
contain additional information: attributes or operations Generalization is implemented as
inheritance in object-oriented programming languages A generalization is an "is a" relationship between classes Notice that this is a relationship between classes, not between object instances A class can inherit from another class, but an object instance cannot inherit from another object
The specific class of the generalization relationship (the subclass) inherits all attributes, operations,
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 12and associations from the general class of the relationship (the superclass) The UML notation for this relationship uses a solid line as a link between classes on the class diagram To distinguish between the subclass and the superclass in the relationship, a small hollow triangle is inserted
between the link and the superclass in the diagram pointing to the superclass
Figure 14-5(c) shows the two classes, Circle and Cylinder, linked with the generalization
relationship In this design, Circle is treated as the generalization (superclass), and Cylinder is treated as the specialization (subclass)
If a class is used as a superclass for several specializations, each class is represented on the class diagram separately, and each specialization class is linked to the superclass with a separate link with a separate triangle pointing to the superclass It is common to use only one triangle pointing to the superclass and the link each subclass with this triangle Figure 14-5 (d) shows class Account,
which is used as a generalization, and two other classes, SavingsAccount and CheckingAccount,
which represent different specializations of the class Account.
If a subclass is used as a generalization for another class, this other class becomes its specialization, and the same notation is used A class can inherit from one class and be used as a base class for another class This gives rise to tree-like inheritance hierarchies in UML class diagrams
Basic UML: Notation for Multiplicity
Most relationships are binary relationships¡Xthey link two classes Well, actually this is not so Recall the previous discussion between the classes Person, Car, and Registration. It is a
ternary relationship: it involves objects of three classes And the difficulty that I had during the discussion of this relationship stemmed from the fact that I was trying to represent a ternary
relationship as a set of binary relationships
Even though the UML supports notation for the ternary relationship, it does not support notation for relationships between the objects of more than three classes Even if it did, when it comes to
implementation of relationships, the C++ language supports only binary relationships; the link
between two objects is established using a physical or a conceptual pointer So, the relationships we model in the UML class diagram are binary relationships¡Xthey link objects of two classes
There is one exception to this observation Sometimes, the relationship connects objects that are instances of the same class For example, an object of class Person that plays the role of the
supervisor might be associated with an object of class Person that plays the role of a team worker
In this case, both objects are of the same class This is a nice theoretical oddity, and most books on UML have examples of this reflexive (or recursive) relationship In practice, it is more convenient
to model the supervisor with the class Supervisor and to model the team member with the class
TeamMember. It is useful to have two different classes in the model because they implement
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 13different responsibilities And if they have too many features in common, well, you can always introduce class Person as their common base.
So, most relationships in UML class diagrams are binary relationships Each link connects two objects of two different classes¡Xone object at one end of the link and another object at the other end of the link Each link links two objects, not three or four
Sometimes, an object of one class might be related to more than one object of another class For example, an object of the class Supervisor might be associated with several objects of the class
TeamMember. On the UML class diagram, you will still have one link between the classes
Supervisor and TeamMember, but you will use additional UML notation to indicate multiplicity
Figure 14-6 shows examples of indicating multiplicity on class diagrams Figure 14-6(a)
demonstrates two classes, Point and Rectangle, in an application where each Rectangle object
is associated with exactly two Point objects, no more, no less UML notation that can be applied to associations can be applied to aggregations as well Figure 14-6(b) demonstrates the relationship between the same two classes, Point and Rectangle, which is treated as an aggregation rather than as a general association
Figure 14-6 UML examples indicating multiplicity for relationships.
Notice that Figure 14-3(c) indicates that class Rectangle has two attributes of class Point, pt1
and pt2. This means that any object of class Rectangle is associated with exactly two objects of class Point. Hence, the link between classes in Figure 14-6(a) or (b) conveys exactly the same analysis and design information as does the class diagram Some experts are upset by this
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 14redundancy and recommend that you use only one way of expressing this information.
The preferred way to do this is to indicate the association and omit the attributes in the class design The rationale for this approach is that the associations represent the analysis and design point of view and the attributes represent the implementation point of view So at the stage of analysis you indicate relationships, and during implementation you implement these relation-ships with
appropriate pointers, data members, and so on I am not sure whether this discussion is important from the practical point of view To me, it looks like splitting hairs Since you don't need to worry about the UML compiler, you probably should do whatever your intuition tells you
If the end of the association or aggregation link is unadorned, this means that exactly one object of this class is required for the relationship to be operational Figures 14-6(a) and (b) show that the presence of exactly one object of the class Rectangle is mandatory
Sometimes, the relationship between objects is not fixed, and its multiplicity changes during
program execution For example, class History in Chapter 12, "Composite Classes: Pitfalls and Advantages," is associated with class Sample. Actually, the relationship is the relationship of
composition: The object of class History contains an array of objects of class Sample. As you can see from Listing 12.4, at the beginning of the program execution, there are no valid Sample objects
in the array During execution, the measurement samples arrive, and information is stored in the array until either the program terminates or the number of Sample objects reaches its
maximum¡Xeight objects
UML allows you to represent this kind of variable multiplicity by indicating the range of the
associated objects Figure 14-6(c) shows an example in which the number of associated objects can change from zero to eight If the number of objects cannot become less than one, the range starts with 1 rather than with 0, for example, 1¡K8
Often, the ranges of objects in relationships are artificial and reflect implementation considerations rather than the nature of the application domain Why, for example, is the number of Sample
objects in the measurement history limited to eight? Because C++ does not allow me to define an object without specifying its length, and I had to specify a number The number 8 looked as good as any other, but there is nothing in the application domain that says that 8 is better than 10, 20, 100,
or any other number
From the conceptual point of view, the number of samples in the history should not be limited For the same reason, the implementation should not force the designer to commit to a specific number
Listing 12.7 shows the container class with dynamically allocated memory, which implements this conceptual model Figure 14-6(d) shows the notation for unbounded multiplicity
Case Study: A Rental Store
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 15Let us consider an application that keeps track of customers borrowing and returning movies at a video rental store.
For simplicity's sake (assuming that the rental store is relatively small and the computer memory is relatively large), the application loads the database of customers and rental items into computer memory at the beginning of the execution The rental item data includes the item title, quantity on hand, and the item id The customer data includes the customer name and phone number (with the phone number used as the customer id), the number of movies the customer has borrowed, and their ids
Even though this example is very simple, it has sufficient details for illustrating the basic issues of designing classes, setting up their relationships, and optimizing the design from the point of view of keeping dependencies between classes to a minimum
To make it more interesting from the point of view of using inheritance, I will add the following detail: The movie data is stored in a file with a letter indicating the movie category ("f" for feature,
"c" for comedy, "h" for horror) When the data is read into memory, the information about the
category is stored in numeric form (1 for feature, 2 for comedy, 3 for horror) When the movie information is displayed on the screen, the category is displayed as a word ("feature" for feature,
"comedy" for comedy, "horror" for horror) When the data is stored back to the file, the movie
category is stored as a letter again
When a customer brings a movie to the register to check it out, the store clerk enters the customer's phone number for the search of the database If the customer is not found, a message is displayed
If the customer is found, the customer name and phone number are displayed along with the data about the movies that the customer has on loan After verifying the customer name, the clerk enters the movie id The quantity on hand for this movie is decremented by one, and the movie id is added
to the list of ids of the movies borrowed by this customer
If a barcode reader is used to enter the movie id, then the movie will definitely be found in the
database In this prototype, when the movie id is entered manually, an error message is displayed if the movie is not found
When the customer brings a movie to the register to return it, the store clerk again enters the
customer phone number When the customer record is displayed, the clerk enters the movie id If the id is found in the list of movies borrowed by the customer, it is deleted from the list, and the quantity on hand for that movie is incremented by one If the movie id is entered incorrectly, an error message is displayed
For simplicity of the example, I omitted the monetary aspect of the program (charging rental fees and late fees), the performance part of the program (accumulating the indicators of demand for each
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 16movie), and the management part of the program (adding, deleting, and editing customer and movie data).
Classes and Their Associations
The list of classes for an application is often compiled by analysis of the functional specification or another document that describes system user interface and behavior
Some experts recommend listing all of the nouns from the system description as a good starting point Other experts ridicule this approach because most of the nouns describe entities that do not rise to the level of a class (phone number, quantity on hand, and so on), and they will later wind up
as attributes (data members) rather than classes
One of the caveats of using the system description for building the model is that the description concentrates on the entities that interface with the system (e.g., customer, store clerk, database) The goal of modeling is to eventually produce the system implementation, which consists of classes that contain data and operate on that data (e.g., Customer, StoreClerk, and Database) The
entities from the system description and classes from the system implementation might have the same names, but they are not identical
Skipping details, let us assume that the class model should include the following classes: Item
(information about a movie item), Customer (information about a customer), Inventory
(managing the set of items and the set of customers), File (managing the database of inventory items and customers), and Store (managing the user interface and requesting services from other classes) Figure 14-7 shows each of these classes together with their attributes and operations
Figure 14-7 UML notation for classes with their attributes and operations.
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 17The meaning of most attributes and operations in Figure 14-7 is evident What is not evident now will become clearer during further discussion.
self-What classes are associated with what classes here? I have to admit that my system description is not very helpful in figuring that out On the other hand, I am in no hurry to admit my fault After all, the description is usually written to help one build and test the program, not to facilitate
drawing an UML model of the system
I think the best way to learn how to create class models is to try to create several alternatives for a simple application, then implement each alternative and evaluate each implementation from the point of view of its complexity The application of the size of this example is a good tool for this type of learning
It looks, however, like I am in the minority on this point Most books on object-oriented analysis and design find it appropriate to give you examples of class diagrams using the system description
as the starting point, without further implementation and, most important, without evaluation of how the model affects the complexity of the solution Meanwhile, the decisions on how to allocate attributes and operations among classes and how to link the classes with relationships affect the
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 18complexity of the program and therefore its reusability and maintainability.
Obviously, the classes Item and Customer should play the role of servers for other classes: They provide such services as saving and retrieving the values of data members
In multifile projects, each class specification is placed in a separate header file The header files are included in the source files that implement the clients of the class, that is, the source files that use the name of the class to define their variables or parameters Listing 14.6 shows the header file for the Item class with its data members and member functions The class provides the data members for the movie title, id, quantity on hand, and category The methods allow the client code to set the data member values of an Item object and retrieve the object's id, quantity, and all four data
members They also allow the client code to print the item data in the required format (without quantity on hand) and increment (or decrement) the quantity on hand
Notice the use of conditional compilation directives According to the rules inherited from C, a header file can be included only once in the source files of your program If you include the header file in more than one source file, the class type definition will be compiled with each source file Since each source file can be compiled separately and into a separate object file, the program winds
up with several definitions of the same type, and the linker is going to complain about that Of course, it would be much easier to change the rule and let the linker discard extra definitions when
they have the same structure This is why every C++ programmer should put these conditional compilation directives into every header file What a pity.
Example 14.6 Class specification for the Item class (file item.h).
void set (const char *s, int num, int qty, int type);
int getQuant() const;
int getId() const;
void getItem(char* name, int &num, int& qty, int &type) const;
void printItem() const;
void incrQty(int qty);
} ;
#endif
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 19Listing 14.7 shows the header file for class Customer. You see the same set of conditional
compilation directives to the preprocessor as in Listing 14.6 The Customer class provides data members for storing the customer name, phone number, the count of movies on loan to the
customer, and the id for each borrowed movie Its member functions allow the client code to set the values of customer name and phone number, add a movie id to the list of movies, remove a movie
id from the list of movies, and retrieve the customer name, phone number, and list of movies
borrowed by the customer
Example 14.7 Class specification for the Customer class (file customer.h).
void set(const char *nm, const char *ph);
void addMovie(int id);
int removeMovie(int id);
void getCustomer(char *nm, char *ph, int &cnt, int m[]) const;
} ;
#endif
Similar to header files, the source C++ code for each class in a multifile project is implemented in a separate source file Listing 14.8 shows the class implementation for class Item. The header file "
item.h " has to be included in this file to make sure that the compiler knows what the scope
operator Item:: means
The implementation indicates that class Item does not need any other classes to support its code It does need library facilities, and some designers include the library components in their UML
diagrams as servers of their classes I think that this is excessive You are interested in relationships between components of your program, not in the extent to which your program uses library classes and functions
To make tracing the links between classes easier for you, I have used line comments to indicate where each Item method is called from This is a good practice that should make the life of the maintainer easier The comments say that class Item is a server to classes Inventory and File.
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 20Example 14.8 Implementation of class Item (file item.cpp).
// file item.cpp
#include <iostream>
using namespace std;
#include "item.h" // this is a necessity
void Item::set (const char *s, int num, int qty, int type)
{ strcpy(title,s); id=num; quant=qty; category=type; }
int Item::getQuant() const // used by Inventory::checkOut()
{ return quant; }
int Item::getId() const
{ return id; } // in printRental(), checkOut(),
checkIn()
void Item::getItem(char* name, int &num, int& qty,
int &type) const // used by File::saveItem()
{ strcpy(name,title); num = id;
qty = quant; type = category; }
void Item::printItem() const // used by printRental()
{ cout.setf(ios::left,ios::adjustfield);
cout.width(5); cout << id; // it knows its print formats
cout.width(27); cout << title;
switch (category) { // different item subtypes
case 1: cout << " feature"; break;
case 2: cout << " comedy"; break;
case 3: cout << " horror"; break; }
cout << endl; }
void Item::incrQty(int qty) // used in checkOut(), checkIn()
{ quant += qty; }
Similarly, Listing 14.9 shows the implementation file for class Customer. The header file "
customer.h " is included in this file; for any implementation file, the header for this file should be included in addition to the header files for all server classes that this class is using
You see that the source code file " customer.cpp " does not include any other header files This means that class Customer does not have server classes¡Xit serves other classes itself The line comments in each function indicate where the function is used as a server to provide the client code with access to customer data and services
Example 14.9 Implementation of class Customer (file customer.cpp).
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 21void Customer::set(const char *nm, const char *ph)
{ strcpy(name,nm); strcpy(phone,ph); } // in appendCust()
void Customer::addMovie(int id)
{ movies[count++] = id; } // in appendCust(), checkOut()
int Customer::removeMovie(int id) // used in checkIn()
{ int idx;
for (idx=0; idx < count; idx++) // find the movie
if (movies[idx] == id) break;
if (idx == count) return 0; // give up if not found
while (idx < count - 1)
{ movies[idx] = movies[idx+1]; // shift tail to the left
idx++; }
count-; // decrement movie count
return 1; } // report success
void Customer::getCustomer(char *nm, char *ph, // saveData()
int &cnt, int m[]) const // Inventory::getCustomer() { strcpy(nm,name); strcpy(ph,phone); cnt = count;
for (int i=0; i < count; i++)
m[i] = movies[i]; }
The Customer constructor initializes the count of borrowed movies to zero Method set() assigns new values to the customer name and phone number, and method addMovie() appends the new id number to the end of the list of movies on loan
Method removeMovie() first checks whether the movie id is found in the list of customer movies (This is not necessary if a reliable method of data entry is available.) If the id is not found in the list, the function returns zero to indicate its failure If the id is found in the list, the method shifts remaining id numbers one position to the left, decrements the count of valid movie ids, and returns
1 to indicate success
Notice that it is the count of ids that is decremented, not the number of values in the array The number of values in the array does not decrease¡Xthe two last array components have the same value of the id after the shift This is why I say "the count of valid movie ids" rather than "the count
of movie ids."
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 22If you feel uncomfortable with the index limits used in the shifting algorithm, you are not alone Shifting algorithms often contain errors that are hard to find One way to make this algorithm easier
to understand is to decrement the count of movie ids before the shift to the left rather than after the shift
int Customer::removeMovie(int id) // used in checkIn()
{ int idx;
for (idx=0; idx < count; idx++) // find the movie
if (movies[idx] == id) break;
if (idx == count) return 0;
count-; // decrement movie count
while (idx < count) // more conventional form
{ movies[idx] = movies[idx+1]; // shift tail to the left
while (idx < count)
movies[idx] = movies[idx++]; // concise but risky
Recall that assignment in C++ is an expression, and C++ guarantees the order of evaluation of operators in the expressions but not the order of evaluation of operands This is correct: the order of evaluation of components in an expression is not guaranteed If the expression is evaluated from left to right, the loop above works fine If the expression is evaluated from right to left, the loop is
in error It is always better to produce more verbose code that is easy to follow than concise code that confuses the maintainer
Similar to the file " item.cpp " in Listing 14.7, I have used line comments to indicate clients of
Customer methods These comments show that class Customer is used as a server by class
Inventory.
The next class to be discussed is class Inventory.Listing 14.10 shows the header file for class
Inventory. Its data members include a list of items and one of customers, the counts of valid
components in each list, and the indexes for accessing components in each list Its member
functions allow the client code to append a movie to the item list and append a customer to a
customer list, retrieve the current item from the list (pointed to by index itemIdx,) retrieve the
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 23current customer from the list (pointed to by index custIdx,) print out the information describing the movie borrowed by the customer, check the movie out, and check the movie in.
Example 14.10 Class specification for the Inventory class (file inventory.h).
int itemCount, custCount;
int itemIdx, custIdx;
public:
Inventory ();
void appendItem (const char* ttl, int id, int qty, int cat);
void appendCust (const char* nm, const char* ph,
int cnt, const int *m);
int getItem(Item& item);
int getCustomer(char* nm, char* ph, int &cnt, int *m);
void printRental(int id);
int checkOut(int id);
void checkIn(int id);
} ;
#endif
Since class Inventory is the client of classes Item and Customer, the Inventory header file
should include the header files for classes Item and Customer.
Some programmers feel insecure about these dependencies, and they include all project header files
in every implementation file "just in case." As they say, it is better to be safe than sorry
This is incorrect There is no risk of including less than is necessary, but there is harm in including more than is necessary If you include less than is necessary, the compiler will flag the lines, which use undefined names, as errors If you include more than is necessary, you will eliminate the risk of reading an error message, but you will make reading more difficult for the maintainer and the client programmer
The compiler will just ignore redundant type definitions Human readers will also ignore them but
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 24only after inspecting class code and finding out that these type names are not used in the class This is a wasted effort and an extra load on human thinking And this is prone to errors in
understanding
Here, I agree with the risk-takers (especially because there is no risk in excluding unnecessary
header files) It is not a good idea to include extra header files "just in case." Instead of avoiding syntax errors, you will confuse the human readers
Implementation of class Inventory is shown in Listing 14.11 You see that it is only the header file
" inventory.h " that is included The objects of the type Item and Customer are used in this file, but the compiler will not complain that these type names are not known By virtue of the fact that
Item and Customer header files are included in file " inventory.h ", they are included in the
Inventory class implementation as well
Similar to Listing 14.6 and 14.8, I have included line comments that indicate from what part of the server code each method is called Unlike classes Item and Customer, class Inventory has only one client class¡Xclass Store.
Example 14.11 Implementation of class Inventory (file inventory.cpp).
{ itemCount = itemIdx = 0; custCount = custIdx = 0; }
void Inventory::appendItem (const char* ttl, int id,
int qty, int cat)
{ if (itemCount == MAXM) // used in loadData() { cout << "\nNo space to insert item"; }
else
{ itemList[itemCount++].set(ttl,id,qty,cat); } }
void Inventory::appendCust (const char* nm, const char* ph,
int cnt, const int *movie)
{ if (custCount == MAXC) // used in loadData()
{ cout << "\nNo space to insert customer"; return; }
Trang 25item = itemList[itemIdx++];
return 1; }
int Inventory::getCustomer(char* nm, char* ph, int &cnt, int *m)
{ if (custIdx == custCount) // in findCustomer(), saveData() { custIdx = 0; return 0; }
custList[custIdx++].getCustomer(nm,ph,cnt,m);
return 1; }
void Inventory::printRental(int id) // used in findCustomer()
{ for (itemIdx = 0; itemIdx < itemCount; itemIdx++)
{ if (itemList[itemIdx].getId() == id)
{ itemList[itemIdx].printItem(); break; } }
itemIdx = 0;}
int Inventory::checkOut(int id) // used in processItem()
{ for (itemIdx = 0; itemIdx < itemCount; itemIdx++)
if (itemList[itemIdx].getId() == id) break;
{ cout << " Movie is not found\n";
itemIdx = custIdx = 0; return; }
for (itemIdx = 0; itemIdx < itemCount; itemIdx++)
{ if (itemList[itemIdx].getId() == id)
{ itemList[itemIdx].incrQty(1); break; } }
itemIdx = custIdx = 0;
cout << " Movie is returned\n"; }
The Inventory constructor initializes the indexes and counters of items and customers¡Xinitially, both lists are empty Method appendItem() and appendCust() are simple: They test for available space (this test is appropriate for the prototype but is redundant when memory is managed
dynamically), add the component at the end of the array, and increment the count of valid
components
Methods getItem() and getCustomer() retrieve the object data from the array at the given index (itemIdx for an Item object, custIdx for a Customer object) In one case, I retrieve the whole object; in another case, I retrieve the values of the object data members Therefore, in one case, it is the client code that cranks out the values of data members, and in another case it is class
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 26Inventory that does that on behalf of the client code This is inconsistent¡Xand harmful It results
in different behavior in the client code and makes the client code more difficult to read
Method printRental() uses the movie id to find the movie in the array itemList[]. If the movie
is found, the printItem() message is sent to the object
Method checkOut() with the movie id as the parameter searches for the item in the array
itemList[]. If the item is not found, it quits and returns 0; if the item is found but the quantity on hand is zero, it quits and returns 1 If the item is available, it decrements the quantity on hand for that item, adds the movie id to the list of movies borrowed by the customer, and returns 2
Method checkIn() also uses the movie id as the parameter It searches for the item in the list of movies borrowed by the customer by calling the removeMovie() method If the movie is not found,
checkIn() prints a message and quits If the movie is found in the customer list (and then removed from the list), checkIn() searches for the item in the array of items itemList[], increments
quantity on hand, and prints the confirmation message
The interfaces of methods checkIn() and checkOut() are inconsistent The method checkOut()
is not involved in the user interface dialog Instead, it returns a value that the client has to analyze and prints the message depending on the return value The work is pushed to the client The method
checkIn() is responsible for the analysis of the error conditions and the corresponding user
interface It hides the error conditions from the client and returns a void value
The next class to be discussed is class File. It is designed to access physical files that contain item and customer data before and after the program run Figure 14-8 shows a sample input file with movie data Each line in the file corresponds to one item: movie title (left-aligned), id number, quantity on hand, and category (as a letter)
Figure 14-8 Sample input file with movie data.
Figure 14-9 shows a sample input file with customer data Each customer is allocated two lines The first line contains the customer name and the customer phone number The second line
contains the number of movies the customer has on loan and the list of movie access numbers
Figure 14-9 Sample input file with customer data.
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 27The format of the output file is the same as that of the input file Figure 14-10 shows the contents of the output item file after a program run It shows that one copy of "Splash" was checked in and one copy of "Gone with the Wind" was checked out.
Figure 14-10 Sample output file with movie data.
Figure 14-11 shows the contents of the customer file after the program run It indicates that
customer Shtern returned the movie with the id number 101 and checked out the movie with the id number 103
Figure 14-11 Sample output file with customer data.
Listing 14.12 shows the class specification for class File. This class encapsulates the fstream file object that is capable of both reading and writing data The class implements public methods
getItem() and saveItem() that perform input/output operations on Item data It also implements public methods getCustomer() and saveCustomer(), which perform input/output operations on
Trang 28class File
{ fstream f;
static void trim(char buffer[]);
enum { TWIDTH = 27, IWIDTH = 5, QWIDTH = 6,
NWIDTH = 18, PWIDTH = 16 } ;
public:
File(const char name[], int mode);
int getItem(char *ttl, int &id, int &qty, char &type);
void saveItem(const Item &item);
int getCustomer(char *name, char *phone, int &count, int *m);
void saveCustomer(const char *nm, const char *ph,
to getline() raises the end of file condition if the line just read is the last line in the physical file When this is the case, the file object becomes null, and getItem() returns zero to indicate the end
of input data to the caller (class Store) Otherwise, it returns one, indicating that there is more data
to be read
The method saveItem() saves item data to the physical file To make sure that the integer category
is converted into the corresponding letter correctly, it uses the switch statement
The method getCustomer() reads the customer name, trims the trailing blanks away, reads the customer phone and count of movies on loan, and then reads the ids of the movies on loan
Method saveCustomer() writes to the physical file customer name, phone, count of movies, and the movie ids
Method trim() strips the trailing blanks from the name because getline() does not stop when it finds the end of the word in the input file It needs either a given number of characters to read or the terminator (carriage return) The string to be trimmed is passed as a parameter The method trim()
does not deal with other data members of the class Hence, the trim() function should be declared
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 29static Also, the method trim is called only from File methods getItem() and getCustomer().
Hence the trim() function should be declared private
Example 14.13 Implementation of class File (file file.cpp).
// file file.cpp
#include <iostream>
using namespace std;
#include "file.h" // this is a necessity
File::File(const char name[], int mode)
{ f.open(name,mode); // used in loadData(), saveData()
if (f.fail()) // if (f.is_open()) is OK, too { cout <<" File is not open\n"; exit(1); } }
int File::getItem(char *ttl, int &id, int &qty, char &type)
{ char buffer[200]; // in loadData()
f.get(buffer,TWIDTH);
trim(buffer);
strcpy(ttl,buffer); // it knows file structure
f >> id; f >> qty; f >> type; f.getline(buffer,4);
if (!f) return 0;
return 1; }
void File::saveItem(const Item &item) // in saveData()
{ char tt[27]; int id, qty, type;
int File::getCustomer(char *name,char *phone,int &count,int *m)
{ char buffer[200]; // in loadData()
Trang 30return 1; }
void File::saveCustomer(const char *nm, const char *ph,
int cnt, int *m) // in saveData()
{ f.setf(ios::left,ios::adjustfield); f.width(NWIDTH);
f << nm;
f.setf(ios::right,ios::adjustfield); f.width(PWIDTH);
f << ph << endl << cnt; // it knows file structure
for (int i=0; i < cnt; i++)
This file will not compile without including the " inventory.h " header file¡Xthe compiler will not know what the name Inventory means However, it is possible to compile this file without the "
file.h " header file because the name of the class File is mentioned only in the implementation of class Store member functions (see Listing 14.15)
Hence, you can get away with including the " file.h " header file in the implementation file and not in the header file for class Store. The compiler will have no difficulty figuring that out This is probably not a good idea from the point of view of human comprehension It is better to keep all server header files in one place, in the header file of the class, to make it easy for the maintenance programmer to immediately see what server classes this class uses
Some designers go so far that they include the header files for the servers of the servers, for
example, " item.h " and " customer.h " This is probably too much¡Xit creates unnecessary
clutter in the client header files
As Listing 14.14 demonstrates, class Store has no data members This would be the alarm signal for a class in the middle of the hierarchy of classes, but is quite all right for a top-level client class The methods of class Store are responsible for high-level operations that describe the external interfaces of the system: loading the database at the beginning of system execution, finding the customer in the database, processing requests for renting and returning movies for the customer,
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 31and saving the database at program termination.
Example 14.14 Class specification for the Store class (file store.h).
void loadData(Inventory &inv);
int findCustomer(Inventory& inv);
void processItem(Inventory& inv);
void saveData(Inventory &inv);
} ;
#endif
Listing 14.15 shows the implementation of class Store. Method loadData() creates a local File
object and sends to it the getItem() messages to read data from the external file Each set of item data is used as arguments in the call to appendItem(). This message is sent to the Inventory
object, which loadData() receives as its parameter Then loadData() creates another local File
object, reads customer data from the file and saves it to the Inventory object The local File
objects disappear when loadData() terminates This terminates the connection between physical files " Item.dat " and " Cust.dat " and the File objects
Example 14.15 Implementation of class Store (file store.cpp).
// file store.cpp
#include <iostream>
using namespace std;
#include "store.h" // this is a necessity
void Store::loadData(Inventory &inv)
{ File itemsIn("Item.dat",ios::in); // item database
char ttl[27], category; int id, qty, type; // item data
cout << "Loading database ¡K " << endl;
while (itemsIn.getItem(ttl,id,qty,category)== // read in
{ switch (category) { // set category for the subtype
case 'f': type = 1; break;
case 'c': type = 2; break;
case 'h': type = 3; break; }
inv.appendItem(ttl,id,qty,type); }
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 32File custIn("Cust.dat",ios::in); // customer database
char name[25], phone[15]; int movies[10], count;
while (custIn.getCustomer(name,phone,count,movies)==1)
{ inv.appendCust(name,phone,count,movies); } } // pump data
int Store::findCustomer(Inventory& inv)
{ char buffer[200]; char name[25], phone[13];
int count, movies[10];
cout << "Enter customer phone (or press Return to quit) ";
{ cout << "\nCustomer is not found" << endl;
return 1; } // give up if not found cout.setf(ios::left,ios::adjustfield);
cout.width(22); cout << name << phone << endl; // print data
for (int j = 0; j < count; j++)
{ inv.printRental(movies[j]);} // print movie Id's
cout << endl;
return 2; } // success code
void Store::processItem(Inventory& inv)
{ int cmd, result, id;
cout << " Enter movie id: ";
cin >> id; // search attribute
cout << " Enter 1 to check out, 2 to check in: ";
cin >> cmd;
if (cmd == 1)
{ result = inv.checkOut(id); // analyze return value
if (result == 0) // not found
cout << "Movie is not found " << endl;
else if (result == 1) // out of stock
cout << "Movie is out of stock" << endl;
void Store::saveData(Inventory &inv)
{ File itemsOut("Item.out",ios::out); Item item; // item file
while (inv.getItem(item)) // no internal structure itemsOut.saveItem(item); // save each item
File custOut("Cust.out",ios::out); // customer output file char name[25], phone[13]; int count, movies[10];
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 33cout << "Saving database ¡K " << endl;
while(inv.getCustomer(name,phone,count,movies)) // pump data
custOut.saveCustomer(name,phone,count,movies); }
Method findCustomer() prompts the operator for the customer phone and terminates (returning zero) if the operator just presses the Enter key without entering any data If the phone number is entered, findCustomer() retrieves each customer data sending the getCustomer() message to the
Inventory object, which is passed to findCustomer() as the argument If the phone number is not found, an error message is printed and findCustomer() returns 1 to notify its client Otherwise, the customer name, phone number and the movie data are printed and the method returns 2
Method processItem() also has a parameter of type Inventory. The method prompts the
operator for the movie id and for the command (to check in or to check out) and then sends either the checkOut() message or the checkIn() message to its parameter When checkOut() returns,
processItem() analyzes the return value and prints the corresponding message When checkIn()
returns, processItem() just terminates because it is checkIn() that analyzes the results of the operation and prints the messages to the operator
Method saveData() mirrors the actions of loadData(). It creates local File objects and sends them saveItem() and saveCustomer() messages with information that saveData() extracts from its Inventory parameter by using messages getItem() and getCustomer().
The last element of the program is the client of Store, the function main(). Listing 14.16 shows that main() instantiates two objects, one of class Inventory and one of class Store. It sends
messages to the Store object and passes the Inventory object as an argument to these messages
Example 14.16 Implementation of function main() Store (file video.cpp).
Inventory inv; Store store; // define objects
store.loadData(inv); // load data
while(true)
{ int result = store.findCustomer(inv); // check results
if (result == 0) break; // terminate program
if (result == 2) // 1 if not found
store.processItem(inv); } // process the cassette
store.saveData(inv); // save database
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 34return 0;
}
Figure 14-12 shows the sample of the program execution It is only a sample; it would be too
cumbersome to demonstrate the complete testing sequence This sample corresponds to the input files shown in Figures 14-8 and 14-9 The output files produced by the execution are shown in
Figures 14-10 and 14-11
Figure 14-12 Example of the execution of the program in Listings 14.6-14.16.
Presumably, the list of classes that the application implements corresponds to the list of real-life entities that the system has to deal with This is why these classes are derived from the analysis of the functional specification Usually, the distribution of responsibilities between classes in the application is quite natural It is natural that class Item maintains the information about movies but not about customer names or disk files
This is natural and relatively easy Things become less certain when it comes to client classes at the top of the hierarchy of classes Class Store does not have any intuitively clear responsibilities The division of responsibilities between class Store and main() is completely arbitrary Some
designers feel that main() should have no responsibilities It should instantiate the top object of the application, and actions should originate from that constructor call
With such an approach, the contents of main() would be moved to the Store constructor The
Store object would not be needed in the constructor because the Store member functions are available in the constructor immediately, without a target object Since Store member functions
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 35are called from the Store constructor only, these member functions do not have to be public¡Xthey can be made private.
class Store {
private:
void loadData(Inventory &inv);
int findCustomer(Inventory& inv);
void processItem(Inventory& inv);
void saveData(Inventory &inv);
public:
Store(void)
{ Inventory inv; // define objects
loadData(inv); // load data
while(true)
{ int result = findCustomer(inv); // check results
if (result == 0) break; // terminate program
if (result == 2) // 1 if not found
processItem(inv); } // process the cassette
saveData(inv); } // save database
On Class Visibility and Division of Responsibilities
The case study described in Listings 14.6-14.16 presents a good opportunity for a discussion of relationships among classes
One of the important ideas discussed in the first part of the book was the idea of dividing
responsibilities among functions to avoid excessive communication between the functions (and excessive coordination and cooperation among developers) This excessive communication often
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 36results from tearing apart what should belong together and from pushing responsibility up to client functions rather than down to server functions.
In this part of the book, this idea takes the form of dividing responsibilities among classes to avoid excessive communication between the classes and excessive communication among developers responsible for different classes
Excessive communication between classes often results from tearing apart what should belong together: from dividing responsibilities among different functions and different classes so that they have to communicate through function parameters and class data members The more extensive the communication is between classes, the more details that class designers should keep in mind, and the increased likelihood there is of errors
Working with classes also involves pushing responsibilities from client classes to server classes Failure to do so results in simpler server classes but makes client classes more complex and more difficult to understand This makes the task of the client programmer and the maintainer more
complex and error-prone
An additional concept that is relevant only to design with classes and not to design with functions is the concept of class visibility The more server classes a client class uses, the broader is the scope
of attention of the client class designer and maintainer¡Xthey have to study the interfaces of the server classes and understand the constraints on the use of the server classes Decreasing the
number of server classes visible to a client class (that the client designer should know about) makes the program easier to understand and maintain
Conversely, the larger the number of client classes using the same server class, the more sensitive the program design is to the changes to the server class Decreasing the number of client classes to which the server class is visible improves the program stability
Of course, these statements should not be taken to the extreme After all, any program can be
designed using only one class (or no classes at all), and the problems of communication between classes, division of responsibilities among classes, and class visibility to each other happily
disappear Yes, we want to build our program with cooperating classes, but we want the class
communication to be minimized
Using UML class diagrams (similar to the one shown in Figure 14-4) is a good method of
analyzing the structure of the program Class relationships on a class diagram illustrate class
visibility: They show which client classes know about a particular server class Unfortunately, class relationships on UML diagrams cannot illustrate the division of responsibilities among classes, pushing responsibility down to servers, and tearing apart what should belong together For that, you have to analyze the distribution of data members and member functions among classes Class
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 37diagrams similar to ones shown in Figure 14-3 are more useful for that purpose.
Class Visibility and Class Relationships
Figure 14-13 demonstrates the relationships between classes described in the case study in Listings 14.6-14.16
Figure 14-13 The UML class diagram for the program in Listings 14.6-14.16.
The UML class diagram shows that class Inventory "owns" the arbitrary number of objects of classes Item and Customer. In the design of class Inventory, I used the physical limits for array sizes, but these are arbitrary artifacts not related to the conceptual relationships among class
Inventory and the objects it contains From the conceptual point of view, class Inventory can contain any number of Item and Customer objects, and this is what I used in the class diagram in
Figure 14-13
The rest of the class diagram indicates that class Store is the client of classes Inventory and
File, and that main() is the client of class Store and class Inventory, It also shows that class
File is the client of class Item but not of class Customer.
This is the result of the inconsistency I noticed earlier in the design of classes Item and Customer.
The objects of class Item know how to print themselves The objects of class Customer do not
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 38know how to do that This is why class Customer provides the method getCustomer(), which is used by the client code to retrieve the Customer data member for printing.
This inconsistency was further supported by the design of class Inventory. Its method getItem()
provides the client code with the Item object and leaves it to the client code to access the
components of the Item object The Inventory method getCustomer() provides the client code with the Customer components but not with the Customer object This is why class File sees class
Item but does not see class Customer.
The visibility of one class in another class of the same program is an important characteristic that designers can use to minimize dependencies among classes and coordination among developers
When an object is defined as a local object in a client method, it is only this client method where the object is visible The amount of coordination is minimal The example is class File whose objects are defined in Store methods loadData() and saveData() only and are not visible in other classes or in other methods of class Store.
When an object is defined as a data member in a client class, it is visible to all methods of the client class This is a stronger degree of dependency¡Xthe client methods have to coordinate the use of server objects The example is class Item and class Customer, whose objects are defined as data members of class Inventory, and indices custIdx and itemIdx, which point to these objects All methods of class Inventory have access to these two arrays and these two indices This provides greater convenience and flexibility This also requires greater human coordination
Consider, for example, Listing 14.11 where class Inventory is implemented Method
getCustomer(), which is called from the Store method findCustomer(), sets the index
custIdx that points to the Customer object, which will participate in the in or out operation Methods checkOut() and checkIn() access the same object using the same index variable custIdx, but they have to subtract 1 to get to the right object This is an example of
checking-coupling, which is created by access to the same computational object from different methods
When a client object is defined in a method of its own client, its server might be sent to its methods
as a parameter¡Xsorry for the complexity of this statement For example, Figure 14-13 shows that class Store is a client of class Inventory. The client object (Store) is defined as a local variable
of its client (function main().) and the server object (Inventory) is sent to Store methods as a parameter
Listing 14.16 shows the implementation of this relationship The function main() is the client of both classes, Inventory and Store. It defines Inventory and Store objects and sends the
Inventory object to the Store methods as an argument Both the designer of main() and the
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 39designer of Store should know about class Inventory.
This is the familiar issue of information hiding that can now be discussed in terms of object
visibility If the Inventory object is defined as a data member of class Store rather than as a
variable in main(), then it is only the methods of class Store that have access to this object
Pushing Responsibilities to Server Classes
Pushing responsibilities down to server classes is a good way to streamline code in client methods and to eliminate low-level processing details that make the client code more difficult to read and to grasp the meaning of processing
For example, in Listing 14.6, class Item provided the methods getId() and getQuant(). These are general methods, which provide the actual item id and item quantity Because of this generality, this design will satisfy almost any requirements that utilize this data
This is good in a library class, which you want to sell to the largest number of possible clients This
is not so good for a part of the program that you want to design to satisfy specific requirements of specific client classes that belong to the same program or to the next release of the same program With a general "library-type" design, the client classes should be flexible to be able to use the
services that the server classes provide Usually, client classes get more information from servers than they really need, and they have to adapt this information to current client needs
For example, in Listing 14.11, the client function printRental() scans each Item object in class
Inventory and retrieves the value of the Item object id Now printRental() could do whatever it wants to this value, but it need only compare it with the parameter value
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 40void Inventory::printRental(int id) // used in findCustomer()
{ for (itemIdx = 0; itemIdx < itemCount; itemIdx++)
responsibilities would require the client code to pass the parameter value down to the server
function so that the server code can do the work on behalf of the client (compare ids) rather than bringing up information for the client code to process Then the client code would look this way
void Inventory::printRental(int id) // used in findCustomer()
{ for (itemIdx = 0; itemIdx < itemCount; itemIdx++)
{ if (itemList[itemIdx].sameId(id)) // important difference
int Inventory::checkOut(int id) // used in processItem()
{ for (itemIdx = 0; itemIdx < itemCount; itemIdx++)
if (itemList[itemIdx].getId() == id) break;
if (itemIdx == itemCount)
{ itemIdx = custIdx = 0; return 0; }
if (itemList[itemIdx].getQuant()==0) // what is the meaning?
{ itemIdx = custIdx = 0; return 1; }
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com