9.2 Class Design


The ability to design a class is fundamental to developing good object- oriented systems because the class is the basic programming unit - classes define individual abstractions, abstractions related through inheritance, and generalized abstractions in class templates. A good designer is one who can generate ideas for possible classes from a problem description and shape these ideas into a concrete class.

Knowledge of good class design is represented in the form of heuristic rules for discovering a class design and guidelines for evaluating a class design. The heuristics rules suggest things to look for in a specification that might indicate where a class could be defined. These rules have arisen from the accumulated experience of designers. However, designers must remember that heuristic rules do not guarantee a successful design, they may not apply in all cases, and they indicate only a starting point for further work. The guidelines are qualitative measures for evaluating a design. Then can be viewed as a checklist of features that should be possessed by a good design. The designer must make an assessment of whether the class does or does not possess each feature. Because the measures are qualitative, good judgement on the part of the designer is important in applying such measures.

The heuristic rules and guidelines are used together in an interative manner. It is not uncommon that a rule can suggest the beginning of a good class design-the rule triggers some idea in the mind of the designer. The initial idea usually lacks the detail of a complete class design, but the details emerge with additional design work. The qualittive measures can be applied to assess the emerging design. The measures can also help in making decisions about which design of several alternatives is most appropriate. The measures may suggest continuing with the design, backtracking and considering other alternatives, or even eliminating the idea from further consideration.

Discovering Class Design

Ideas for classes can be generated by considering the system specification from different perspectives. As shown in the figure below, three different perspectives are described and illustrated:

Each perspective inspires ideas for possible classes by suggesting general categories of entities to look for in the specification, posing certain kinds of questions that might be asked about the system, and focusing on a particular dimension of the overall system to concentrate the designer's thinking. The combination of these three perspectives is a large category of commonly encountered classes.




Three Perspectives

Behavioral Perspective

The behavioral perspective centers on the actions that a system takes. The analysis of actions is a natural starting point because systems are often described in terms of what the system does. In a similar sense, software is developed to accomplish some end and is purchased because of what it can do. Terms such as "accomplish" and "do" illustrate the fundamental role of a system's activity.

The purpose of identifying an action is to reveal the object that performs the action. In the object-oriented view of software an action is performed by the method(s) of some object(s). Simple actions might be performed by a single object. More complex actions might require the collaboration of several objects. In each case, however, the actions emanate from a performing cast of objects. Natural questions to ask about each action are given in the table below; their answers begin to reveal the components and organization of the underlying object structure.


Behavioral-Perspective Questions
What object initiates the action?

What objects collaborate in performing the action?

What objects are altered by the performance of the action?

What objects are interrogated during the action?


The study of a system's actions often leads to objects that fall into well-known general categories. General categories that are often associated with actions are actor , reactor, agent and transformer.

An actor has a specific purpose, mission, agenda, goal, outcome or plan. It knows what result at least a part of the system is meant to achieve, and it is equipped with the programming to drive the system toward this result. An actor object may embody knowledge about and enforce the sequencing of activities that must occur; contain strategies for evaluating and selecting among different ways to achieve the result; and have ways of coping with exceptional conditons by recovery, backtracking, or graceful termination.

A reactor responds to events which are characterized by the fact that they are asynchronous and cannot be scheduled in advance, such as in an interactive drawing program, where the user may be allowed to select a shape on the screen by clicking a mouse button at any time. Events may originate from a variety of sources, three categories of which are:

  • internal events: events generated by other objects within the system itself. For example, a timer object may generate an event that signals the end of a prescribed interval. The reactor object must determine what should occur at this time and initiate the corresponding action. A specific situation is an animation sequence in which the reactor must arrange for the next step in the animation to occur.

  • external events: events originating in another system. A toolkit, for instance, may have several interacting programs that share data. When one of the programs updates the data, the other programs must react accordingly. A specific case of this is a spreadsheet program that creates the data for a table appearing in a document-formatting program. When the spreadsheet recomputes new values for the table, the formatting system must update its document in response.

  • interface events: events initiated by the user through mouse movements (dragging, clicking), keyboard actions (pressing or releasing keys), or interactive user-interface components (buttons, sliders). The system may contain different reactor objects for these different types of user events.

Regardless of the event's source, the reactor object must determine the appropriate reaction and at least initiate the response. Some reactor objects maintain a state or history of past events, and their reaction to the current event is conditioned by this history. For example, a mouse-event reactor that needs to distinguish between a single click and a double click needs to keep track of information about the recent past. Other reactor objects are stateless and always have the same reaction. For example, a "center text" button always reacts in exactly the same way, whatever the data.

The third category of objects associated with actions are agents which are intermediaries that assists other objects in some way. The purpose of the agent is twofold: first, to relieve the other objects of the responsibility for performing the service that the agent provides; second, to conceal or hide the details of how the agent performs its service. Agent objects promote reuse because they can be used in any system where their service is needed. Agent objects improve the reliability and flexibility of a system through information hiding because no other part of the system is aware of how the agent's service is implemented. An agent may play one of several roles:

  • Messenger: this type of agent provides a data-delivery service through which one part of the system can relay information to other parts of the system. Like postal systems and package delivery companies, the messenger relieves the sender of the responsibility of locating the recipient, arranging for the transportation, tracking the progress of the item, and arranging the delivery. Distributed-mail systems, for example, depend on message agents.

  • Server: this type of agent is essential to the client-server model; common use of servers is as producers or consumers of data. The server relieves its clients of the burden of knowing where and how the data is stored. Servers may obtain the data from local tables, disk files, network connections to remote databases, or generate the data via simulation. In all cases, the client is protected from the details of how the server performs its task.

  • Finder: this type of agent locates particular data or other objects. For example, the messenger agent may employ the services of a finder agent to locate a recipient, or the server agent may use one to locate a particular record in a database. The finder contains knowledge of how and where to search for the desired entity and how to recognize the sought for entity when it is discovered.

  • Communicator: this type of agent engages in dialog with the user. A variety of user-interface devices may be used by the communicator to provide its service; for example, a communicator interacting with a user to obtain the name of a file where data is to be found or placed might conduct a simple fill-in-the-box dialog, allow a user to select a file from a list of filenames that the communictor found in a particular directory, or allow a user to navigate through the file system seeking the desired file.

Many other kinds of agents exist. These four agents are meant only to suggest the kinds of objects included in this category.

The fourth and final category of objects associated with actions are transformers. A transformer object alters in some way the data that passes through it. A transformer often has very little knowledge of either the producer or the consumer of the data and little understanding of the meaning of the data that it is manipulating. These limitations reflect the narrow scope of the transformer object. Two examples of transformers are:

  • Formatter: this type of transformer changes the appearance of data; commonly encountered formatters are displayers, marshallers, and encoders. A displayer object renders its data in a human-readable form. Low-level displayers are similar to the stream I/O objects that change the binary (internal) representation of a value into the string representation shown to the user. A high-level displayer is a paragraph object that knows how to align the text contained in a paragraph and arrange for line breaks, hyphenation, and spacing. A marshaller object is responsible for creating a linearized representation of its data. Marshaller objects are used in file operations to pack data into blocks for more efficient use of disk opertions and in remote operation systems to pack the arguments of an operation into a single buffer. An encoder object is used to change the appearance of the data, though not into a human-readable form. Encoders are used to convert data into a standard representation format so that it can be exchanged with other programs and encrypt the data for secure communication over untrusted networks.


  • Filters: this type of transformer screens data for certain characteristics. The filter removes from the data any item that does not meet its criteria. For example, a filter may remove all lines in a text file that do not contain a search keyword. Other filters might look for patterns in the data, or other more complex search critieria. Web search engines and library catalog searchers are large-scale examples of filters. Transformer objects are often used in a sequenced or pipelined arrangement; a filter transformer may select data from a database, pass the selected items to a displayer transformer to put it into human-readable form, and pass this data through an encrypter transformer for secure transmission.


Structural Perspective

Classes can be discovered by focusing on the relationships described or implied by a specification. A relationship defines a pattern, organization, or structure among a set of entities. Since the entities are likely candidates for classes, discovering and analyzing the relationships is a way of discovering classes. This is a productive approach because the statement of a system's functionality often involves the use of relationships to explain the intended nature of some part of the systems; words or phrases like consists of, uses, enforces, maintains, and group of all indicate relationships.

A relationship can be analyzed by asking questions that identify the classes and objects it implies. The answers to these question lead the designer from the relationship to classes of objects that are in some way involved with it.



Structural-Perspective Questions
What objects are involved in the relationship?

What objects are necessary to sustain (implement, realize, maintain) the relationship?

What objects not in the relationship are aware of and exploit the relationship?

What objects not in the relationship are used by the related objects?


Studying a system's structure often leads to objects that fall into well-known general categories. General categories of objects that are often associated with particular structures are acquaintance, containment, and collection.

The first category of objects that can be discovered from the structural perspective is acquaintance. In an acquaintance structure, some objects know about one or more of the other objects. This is the simplest form of structural relationship that allows one object to interact with another. Concretely, one object would have a pointer or a reference to the object that it knows about. There are two kinds of acquaintance relationships: symmetric and asymmetric.

  • Symmetric: an acquaintance in which the objects are mutually acquainted; they know about one another. For example, in a drawing tool, a Rectangle object and a Canvas object may know about each other. The Canvas is acquainted with the Rectangle, and other similar objects that appear on the Canvas, so that the Canvas knows how to redraw itself. The Rectangle is acquainted with the Canvas because the Rectangle object contains the detailed information on how the Rectangle should be drawn (location, shape, color, etc.) using the methods provided by the Canvas (e.g., DrawLine).

  • Asymmetric: an acquaintance in which the known object is unaware of the knowing object. For example, in a system where a Clock object is started by the user pressing a Start Button, the Button object must know about the Clock object, but the Clock object need not know about the Button object.

There are certain properties that apply to both forms of acquaintance. The acquaintance may be persistent or transitory. A persistent acquaintance is one that lasts for a substantial period of time, perhaps even through the entire execution of the system. A transitory acquaintance lasts for only a short time, maybe just the duration of one method invocation. The acquaintance may also be direct or indirect. In a direct acquaintance, one object may immediately refer to the other. In an indirect acquaintance, some intermediary object must be accessed in order to refer to the known object.


Containment objects may be discovered from the structural perspective as well. In a containment structure, the objects form a part-whole structure. Terms in the specification like contains, consists of, and has are indicative of a containment relationship. Two uses of the containment relationship are:

  • Collaborator: a structure in which the whole is created by the interaction of its parts. Two different areas of a system where collaborative relationships might be sought are in the description of the application-specific entities and in the user interface. An application-specific entity description might state: "A StopWatch consists of a clock display, a Start button and a Stop button. The clock display has two hands and the numerals 1 through 12 arranged in a twelve-hour clock manner." The containment relationship occurs twice in this description. First, the StopWatch is described as having three components contained within it: a clock display and two buttons. Second, the clock display is described as having two components: the hands and the numerals. In a containment relation, the containing entity is represented as a class whose aggregated objects are of classes defined by the contained entities. In the StopWatch example, the StopWatch class would aggregate objects of the ClockDisplay and Button classes. Similarly, the class ClockDisplay would aggregate objects of the ClockHands and ClockNumerals classes. Containment relationships can also be found in the description of the user-interface structure; as in "The drawing tool interface has a drawing area and three menus through which the user can control the operations of the tool." Has denotes the containment relationship between a DrawingTool class and the aggregated objects it contains, namely, an object of the DrawingArea class and three objects of the Menu class.

  • Controller: a controller object exerts substantial, and possibly total, control over its objects. An example of a controller is the Boss in the Boss-Worker model: in this model, units of work, called tasks, are presented to the Boss by client objects. The Boss delegates a task to a Worker, which performs the tasks and delivers the result to the Boss. The result then is forwarded to the client. In a controller structure, the Workers are completely concealed by the Boss and are invisible to the client. The Workers usually have no contact with each other and even may have no contact with other objects except for the Boss object. In some cases, the Workers perform identical actions and the Boss delegates the task to the next available Worker. This situation frequently arises in parallel-processing computations where each Worker may be on a separate processor, or in programs with multiple independent threads where each Worker has its own thread of control. In other cases, each Worker performs a distinct computation. For example, the Boss may play the role of a DataSource while each Worker represents a different, specific source (a LocalFile Worker, a RemoteFile Worker, a DataBase Worker, etc.).


The structural perspective may also reveal collection objects. In a collection structure, the individual objects maintain their external visibility. Collection is similar to containment in that both are groupings, but collection differs from containment in that collection does not totally conceal the grouped objects, whereas containment completely aggregates and hides them. The variations among collections are determined by the nature and the degree of control that is imposed on the group. Three kinds of collections are peer, iteration, and coordinator. The first two of these exert limited control on their objects. Coordinators have greater influence over their members. The three kinds of collections are:

  • Peer: a collection in which objects are of equal status (hence the name) and are undifferentiated by the collection, which imposes no control over them. The collection exists simply to give a name to the logical structure of the objects that it contains and, perhaps, to provide resources for these objects to use. Such a form of collection is described in the statement: "The user interface has three buttons to start, stop, and reset the system." The three buttons form a logical grouping; the word has suggests a possible containment relation. However, the object may not be completely aggregated by the grouping, and so a peer collection might be more appropriate. Thus, a class ControlButtons might be created to represent the group. In practical terms, the ControlButtons might provide resources that are needed by the button objects in the graphical user-interface system. The three button objects are peer objects within the ControlButtons group.

  • Iterator: a collection providing some indexing function over its objects. The grouped objects are not necessarily of the same class, but they have a shared interface that allows the group to realize the indexing function. The indexing may be as simple as the order in which the objects were added to the collection (as, for example, items in a pull-down menu) or more complicated (as, for example, items in a database indexed by a key field).

  • Coordinator: a collection designed to maintain some property or invariant condition among its objects. For example, a RadioButton collection may enforce the property that only one among all of the Button objects in the RadioButton may be in the "pushed" state at any one time. Thus, pushing one of the buttons has the side effect of unpushing any other previously pushed button. The coordinator exerts some control over the objects in the collection, but the coordinated objects maintain their visibility, and can be known and operated upon by objects outside of the collection. The collection is intended not to conceal the coordinated objects but to assist these objects in implementing a group-wide property.


Information Perspective

Classes can be discovered by examining the information content of a system and the way such information is manipulated. This point of view is useful because information processing is a significant aspect of most software systems, as reflected by terms like "information systems" and "information age" in relation to computerized systems. In some cases, information-processing capabilities are the most important services a system can offer to its users. The specifications of these systems are especially concerned with describing the information and its manipulation.

Information can be viewed as data or state. In simple terms, data is the information processed by the system and state is information used by the system to perform that processing. As an example of the difference between data and state, the specification of a document preparation system might refer to the notion of a paragraph. The user of the system defines the words that make up the paragraph?the paragraph's data. When processing this paragraph, the system may have state information such as a variable that determines if the end of paragraph has been reached. This varible determines whether the paragraph processing will continue or terminate. While the distinction between data and state information cannot always be perfectly drawn, the differences are often clear enough that it useful to make the distinction.

The table below ennumerates four factors that distinguish data from state information. Data is user-centered information and is defined in terms of concepts in the application domain. In contrast, state information is primarily an artifact of the system itself: the state information may change if the system design changes, but the data usually remains unaffected by changes in the system (except, of course, if the system is changed to require different data or data in a different format). The lifetime of data exceeds the lifetime of the system's execution, while state information does not. For example, in the document-preparation system, the paragraph data exists before, during, and after the execution of the system. However, the state information indicating the end of paragraph has been reached only has meaning and existence while the program is executing. Finally, data is of direct concern to the user - who is using the system precisely because the system processes the data in some manner. However, the user is not interested in the state information that the system maintains for its own use.



Distinctions Between Data and State
Factor
Data
State
Primary Focus Application/User Program/System
Role processed by system guides processing
Lifetime exists beyond program
execution
same as program's execution
Source/Visibility user system


When data or state information is referred to in a specification, some of the questions that can be asked about the information in order to identify classes and objects related to the information are shown below.


Information Perspective Questions
What objects are needed to represent the data or state?

What objects read the data or interrogate the state?

What objects write the data or update the state?


The study of a system's information often leads to objects that fall into well-known general categories. Some of the object categories often relevant to data are:

General categories of objects that are frequently relevant to data and state information are:

  • Source or sink: a repository of data outside of the system. A source is any entity outside of the program (system) that generates data, and a sink is any entity outside of the program that receives data. An entity can be both a source and a sink; an updatable database is an example. Sources and sinks include local files and databases, remote files and databases, and servers. Objects that directly represent the sources and sinks should always be considered in the design. Reader objects and writer objects are obvious kinds of objects to consider for each source and sink, respectively.


  • Query: the description of the data sought from a source. In the simplest case - reading sequentially from the source - the description is empty; the mere fact that the query is attempted implies what is sought by the program. For a randomly accessed source, the query may simply contain an index of the required record or block of data. In more complicated cases - highly structured databases, for example - the query may contain a wealth of information indicating how to select, combine, reduce, and reorder information in the database. The role of the query object is to provide a simply programmed interface by which this complex information can be managed most effectively.

  • Result: the data returned by a source or directed to a sink. In the simpler cases, the result may be nothing more than a block of data. More complex cases arise when dealing with more complicated data organizations. For example, a digitial library system might return as the result of a query a set of titles that satisfy the query and rank them by some relevance criteria. Here, the object that represents the result has a more intricate structure and a more sophisticated interface. Correspondingly, the program may generate a complex result that has to be transmitted to the sink in a sequence of more primitive, lower-level operations. The purpose of the result object is to provide an interface that relieves the other parts of the system of the burden of being aware of the lower-level details of how the complex data is communicated to sources or sinks.

  • Buffer: multiple logical data items contained in one physical unit. The data is transferred from the source or to the sink in terms of the physical units of data. Buffers are often used to improve the performance of lower-level I/O operations. For example, more efficient use is made of network bandwidth and disk contrlolers if data is written in fewer, larger blocks. A buffer for a sink gathers the data written to it by the program and actually transmits this accumulated data as one physical unit when the buffer is sufficiently full. A buffer for a source reads the data in larger physical units and delivers this data as requested by the program. When the buffer is empty, another physical unit is obtained from the source.

  • Cache: an object anticipating the need for data not yet requested by the program. A cache for a source might read ahead , trying to guess what data the program will request next. If the cache's guess is a good one, the program will discover that its requested data is readily available. This effect, improving the latency of lower-level I/O operations, is the principle motivation for a cache. The extent to which a cache is successful depends on how accurately it can guess the future needs of the program. In some situations, effective cacheing strategies are possible - for instance, a cache for a sink might retain previously written data in expectation that this data might be requested again in the near future. Another example is in a digital library system where it may be a good guess that additional data about the titles given in response to a user's query are likely to be requested in the near future.

  • Synchronizers: enforcers of timing or sequencing constraints. Constraints are usually necessary to ensure the correct operation of a source or sink. For example, a source may only work correctly if read operations are performed sequentially (i.e., each read operation is issued only after the previous read operation has completed). More complex constraints might exist for an updatable database that imposes the restriction that write operations cannot take place while a read operation is in progress. In this case, a synchronizer object is needed to regulate the reading and writing activity.

In addtion to these classes of objects, iterators and transformers, discussed earlier in this section, are also frequently relevant to the information perspective. Iterators and transformers can be discovered from the structural or behavioral perspectives, respectively.

General categories of objects that are frequently relevant to state are:

  • Recorder: a representation of the current state of the system. All information needed to evaluate the current condition of the system is contained in recorder objects. The state information stored includes the processing options that currently apply (e.g., the current drawing color), the current mode of the system (e.g., "add" or "delete" mode), the prevailing boundary conditions ("there can be no more than 100 sides on a polygon"), and program flags (e.g., "shape currently selected"). Because the state information is of such varied character, there may be several classes each of which represents a related part of the current state of the system.

  • Scheduler: able to prioritize a system's currently available courses of action. In sequential programs, any one of a variety of actions may possibly produce the desired answer. For example, in a maze search program, there are several alternative paths to test at each intersection. The role of the scheduler is to determine in what order these possiblities are evaluated. In concurrent programs - programs with multiple independent threads of control - the scheduler is responsible for selecting which thread to execute next from among all threads that are capable of executing. The scheduler may take a variety of application-specific details into account in making its selection.

  • Maintainer: determine the actions to take in the current state of the system and determine the next system state. In simple systems, every action of which the system is capable can be performed at any time. In such simple systems the maintainer is trivial and perhaps not even needed. However, in more complex systems, the system's response is dependent on previous actions ("a paste can only occur after a cut or copy operation"), the current mode of the system ("in add mode a new shape is placed where the user clicks while in delete mode the shape where the user clicks is removed"), or the current recorded state ("if a shape is selected then highlight the shape"). In these more complex systems, the maintainers must be more highly structured; the correct design and implementation of the maintainers can be the most challenging issue faced by a developer. A structured design technique is available to design and implement complex maintainers.


Combining the Three Perspectives

The table below summarizes the combined categories of classes that are discussed above. Though these categories are not exhaustive and are meant only to illustrate the varied kinds of objects that can be discovered by analyzing a specification, they provide a useful foundation for begining the analysis of a specification.



Combining All Three Perspectives
Behavioral
Structural
Information
  • actor
  • reactor
    • internal events
    • external events
    • interface events
  • agent
    • messenger
    • server
    • finder
    • communicator
  • transformer
    • formatter
    • filter
  • acquaintance
    • symmetric
    • asymmetric


  • containment
    • collaborators
    • controller


  • collection
    • peer
    • interators
    • coordinator
  • data
    • sources/sinks
    • query
    • result
    • buffer
    • cache
    • synchronizer

  • state
    • recorder
    • scheduler
    • maintainer

The three perspectives are meant to be used together in combinations. For example, an initial analysis of a specification may discover a data source (information perspective), and study of the data source reveals a need for a filter (behavioral perspective) to process the data from the source. Further analysis may discover that the filter has an asymmetric acquaintance (structural perspective) with a communicator (behavioral perspective). The communicator conveys its output to a buffer (information perspective) that is connected to a sink (information perspective). As illustrated by this example, an object in one perspective may lead to objects in another perspective.


Evaluating A Class Design

A proposed class design must be evaluated to determine whether the proposed design should be accepted, revised, or rejected. In the early stages of design, many ideas for classes may be discovered. In fact, it is good practice to generate as many ideas as can be thought of in a brainstorming and uncritical mode. The goal of this early phase is to discover possible classes. At a later stage, a possible class must be analyzed to determine if it is substantial enough to be represented as a class, if it duplicates or overlaps with another class, or whether it should be refined into two or more other classes.

Five aspects of a proposed class design should be evaluated as shown in the table below. All of these aspects should be considered for each class. It may also be useful to apply them in the order listed since this ordering proceeds from the highest to the lowest level of detail.


Class Design Evaluation
Abstraction: does the class capture a useful abstraction?

Responsibilities: does the class bear a reasonable set of responsbilities?

Interface: is the interface of the class clean and simple?

Usage: is the class design consistent with how it is actually used?

Implementation: is there a reasonable implementation for the class?


The abstraction captured by the class is the first aspect of a class design to evaluate. Recall that capturing an abstraction is the most fundamental role of a class, so this is most critical. The abstraction may come from the application domain (e.g., Automobile or Employee), from the user interface (e.g., StartButton or FileChooser), or from the computation (e.g., ListManager or MenuBar). Specific tests to determine the adequacy of the abstraction are:

  • Identity: simple and suggestive names for the class and for all of its operations found if a class captures an abstraction. Difficulty in finding a good name for a class or its operations is usually a sign of a weak abstraction, as are names that contain vague modifiers. For example, terms like simple or complex in a name should be questioned, particularly if they are not derived from application domain terms. If an application has some labels that contain only letters and digits and other labels that can contain any character, referring to these as "SimpleLabel" and "ComplexLabel" is not helpful. Unless there are more meaningful application terms for these labels, better names might be AlphaNumericLabel and Label. In these names the vague meaning of "simple" has been replaced by a more specific indication of the class's intent and the vague meaning of "complex" has been dealt with by simply eliminating the modifer. The same rule applies to the methods of a class.

  • Clarity: the meaning of a class given in a brief, dictionary-style definition. The definition should be brief (the length of one or two short, declarative sentences), use precise terms, and fully convey the intent of the class. A definition of this form is difficult to write because it requires careful thought and precise use of language. However, the effort to construct this definition leads to a confirmation that the class is well defined, creates a compact reference for potential reusers of the class, and establishes a basis for the class's documentation.


  • Uniformity: the operations defined for the class having uniform level of abstraction. This means that the names and arguments of the class's methods must be at a similar level of conceptualization. For example, consider the following partial class definition:


    class ShapeManager
    					
    

    {...

    public:

    ...

    AddShape(Shape* s);

    RemoveShape(Shape* s);

    ReplaceShape(Shape* by, Shape* with);

    FindRectangle(Rectangle r);

    ...

    };

    In the ShapeManager class, all of the methods refer to objects of the class Shape except for the single method FindRectangle, whose name and argument refers to a lower-level concept. The uniformity of this class can be improved by eliminating the FindRectangle method or replacing it with a FindShape method that is consistent with the level of conceptualization of the other methods of the class.

Second to be evaluated are the responsibilities assigned to the class. A class's responsibilities are what it is expected to remember over time, the effects it is expected to have on other parts of the system, and the services that it is expected to provide for other objects in the system. Specific tests to determine the adequacy of a class's responsibilities are the extent to which the responsibilities are:

  • clear: it should be easy to determine whether or not the class is charged with a specific responsibility. Any doubts about a class's responsibilities signal danger because it then raises the possiblity that the specifier, the implementer, and the user of the class may all have different interpretations of the class's responsibilities. This lack of common understanding can only lead to unwelcome situations.

  • limited: the responsbilities placed upon a class should not exceed those required by the abstraction on which it is based. Adding extraneous responsbilities obscures the purpose of the class, confounds (re)users, and increases the difficulty of implementing the class.

  • coherent: the responsibilities of a class should make sense as a whole. Coherence is lost when two distinct abstractions are represented by the same class. When this occurs, the responsibilities of the class will represent the union of the responsibilities associated with each of the two abstractions. Because the abstractions are distinct, though perhaps similar, the overall responsibilities of the class loses coherence.

  • complete: the responsibilities given to a class must completely capture those of the corresponding abstraction. Not only does a missing responsibility weaken the class of which it should be a part, but it also disturbs the coherence of the other class(es) in which the responsibility is placed.

The interface of the class is the third aspect of a class to be evaluated. A well-designed class interface is clearly important; it is the concrete programming device by which objects of the class are manipulated. Programmers are more likely to (re)use, and to (re)use correctly, a class that has a well-designed interface. Specific aspects of a class's methods to assess are:

  • naming: the names of the methods should clearly express the method's intended effect on the object. Methods that have different effects should be given names that are distinctly different. A class can be confusing and even hazardous to use if the names of methods with different effects are too similar. For example, consider the following class interface:
    class ItemList
    					
    

    {...

    public:

    void Delete(Item item); // take Item's node out of list and delete Item

    void Remove(Item item); // take Item's node out of list but do not delete Item

    void Erase (Item item); // keep Item's node in list, but with no information

    };

    Each of these methods has a different effect on the List and on the parameter Item, but their names are easily confused. Many programming mistakes could easily arise if Delete is used when Remove or Erase is intended. The remedy to this situation might be deeper than a simple change of names in the interface; the entire class design might need to be reconsidered.

  • symmetry: if a class has a pair of inverse operations, the names and effects of the corresponding methods should be clear. A typical example of symmetric operations is the pair of accessor methods for a class property X that might be named GetX and SetX to reflect their mutual relation to the property X. The interface should be reexamined if it has only one of a pair of inverse operations (e.g., a get method but not a set method). Is there a sound design decision to provide only one of the pair? If not, include the missing operation.


  • flexiblity: the methods of the class, particularly the constructor, should be overloaded to provide a variety of different uses. There are three ways in which overloading increases the flexibility of the interface. First, it allows the method to be invoked using the form of data that is most readily available at the invoking site. For example, consider the class interface
    class ItemList
    					
    

    {...

    public:

    ...

    AddItem(Item item, int index);

    Item FindItem(int index);

    ...

    };

    that identifies Item objects by their position ("index") in a list. If it is known that character string data is often available at the invoking site (the index has been read as a character string), then it is reasonable to consider adding overloaded AddItem and FindItem methods that allow the index to be given as a character string. Via this overloading, the class assumes the responsibility of converting the character string to an integer index.

    Second, overloading allows only the necessary arguments to be supplied. The interface can be tailored to allow the invocation to assume more or less control over the details of the operation: the more parameters in an overloading, the more control is given to the invocation. For example, the class below contains several overloadings of a Draw method:

    class DrawingArea
    					
    

    {...

    public:

    ...

    void Draw(Shape s);

    void Draw(Shape s, Color outline);

    void Draw(Shape s, Color outline, Color fill);

    ...

    };

    The programmer using a DrawingArea object can ignore the Color parameters if they are not relevant or if the Colors selected by the DrawingArea object itself are acceptable. However, the programmer is able, when needed, to use the overloadings that allow one or both of the Colors to be specified.

    Third, overloading can be used to relieve the programmer of remembering the order of arguments. For example, the AddItem method in the ItemList class above requires both an Item and an index. An overloadng could be provided so that the Item and the index could be specified in either order. Overloading should only be used, of course, when the overloaded methods all have exactly the same effect on the object.

  • convenience: default values provide a more convenient way to use methods. The parameters of the clas's methods should be arranged in an order that allows default values to be supplied wherever possible. Question whether a default can be provided for each parameter, rearranging the methods or possibly introducing a new method to allow the default to be expressed.

The fourth aspect of a class to be evaluated is its usage. An otherwise well-designed class may lack useful methods because it is often difficult for the class designer to forsee all of the important contexts in which the class may be used. By examiningg how the objects of the class are used in different contexts, it is possible to discover these missing operations. For example, consider the following definition and use of the Location class:

    class Location
     { private:
          int xCoord, yCoord; // coordinates
       public:
          Location(int x, int y);
          int XCoord(); // return xCoord value
          int YCoord(); // return yCoord value
     };
     ...
     //usage
     Location point(100,100);
     ...
     point = Location(point.XCoord()+5, point.YCoord()+10); // shift point

The code to shift the point relative to its current coordinates is possible using the existing interface of the Location class. If the Location class is commonly used in this way, the class designer should consider adding a new method to it. The revised definition and use are:

    class Location
     { private:
          int xCoord, yCoord;            // coordinates
       public:
          Location(int x, int y);
          int XCoord();                  // return xCoord value
          int YCoord();                  // return yCoord value
          void ShiftBy(int dx, int dy);  // shift point relative to current coordinates
     };
     ...
     //usage
     Location point(100,100);
     ...
     point.ShiftBy(5,10); // shift point

This change in the class design improves both the simplicity and readability of the code using the Location class.

The implementation of the class is the fifth and final aspect to evaluate. The implementation is the most detailed, the most easily changed, and the least important of all of the aspects of a class. A class that represents a sound abstraction and has a well-designed interface that can always be given a better implementation if the class's existing implementation is weak. The implementation can, however, point to two ways in which a class can be improved. First, an unwieldy and complex implementation may indicate that the class is not well conceived or is simply not implementable. In this case, the design of the class and its underlying abstraction must be reconsidered. Second, an overly complex implementation may also indicate that the class has been given too much responsibility. If this is the case, then either the class can be partitioned into two or more classes, or new classes can be developed that are used internally by the original class. The first strategy is visible to the users of the class while the second strategy is invisible. In either instance, new classes are developed which may help to reveal previously missing abstractions in the application domain or may simply be useful classes in the computational structure.

 

Read the following description of a data visualization system and then answer the questions below.


Specification of a Data-Visualization System
Many applications in science and engineering disciplines generate large volumes of data that must be understood by the scientist or engineer. Because of the volume of data (megabytes of numerical data), it is difficult or impossible to easily understand the data's import or to recognize critical points.

Develop a data visualization system that provides a scientist or engineer with a two-dimensional graphical representation of a large set of data. Through computer graphics techniques and effective use of colors, your system should allow the user to navigate within the displayed representation, request specific details of a particular part of the data, see the representation change over time, or filter the data on which the representation is based.

The basic functions of the visualization systems are:

selection:
the user must be able to select the file containing the data that is input to the visualization system. The user initiates the selection through a button in the user interface.
representation:
the data is limited to display on a two-dimensional x-y coordinate system. The axis of this system must be drawn and some labeling of the axis must be given. The user, through the file format or through the user interface, must have some means of determining what symbol will be used to represent the data (e.g., a small filled point, an alphabetical letter, a cross-hair).

filtering:
the user must be able to set the limits of the x-y coordinates presented in the display. For example, a user might specify that the data displayed have an xcoordinate between -100.0 and +100.0 while the yvalue be in the range from 50.2 to 125.5. Data values outside of this range are ignored. The axis in the representation must be scaled so that the selected range extends across the entire area allocated to the representation. In other words, by selecting successively smaller ranges, the user will have the effect of zooming in on a part of the data.
timed display
the user must have a way of requesting that the data be displayed in a time sequence with additional data appearing at each time step. It is possible that a different file format may be required to support this option.

The user interface for the data-visualization system contains a display area (a Canvas object) on which the data is displayed on an x-y axis. The user interface also contains a number of TextBoxes in which the user enters the limits of each axis for filtering the data. A number of buttons are present to control different options or initiate different functions (e.g., open a new file, begin timed display).


  1. Using the behavioral perspecive, identify several classes for the Data- Visualization System.

  2. Evaluate the classes that are found in terms of their abstraction, responsibilities, and interface.

  3. Using the structural perspective, identify several classes for the Data Visualization System.

  4. Evaluate the class that are found in terms of the abstraction, responsibilities, and interface.

  5. Using the information perspective, identify several classes for the Data- Visualization System.

  6. Evaluate the classes that are found in terms of their abstraction, responsibilities, and interface.

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement

 

ÿ