Roles and Activities > Designer > Class Design

Purpose
  • To ensure that the class provides the behavior the use-case realizations require.
  • To ensure that sufficient information is provided to unambiguously implement the class.
  • To handle non-functional requirements related to the class.
  • To incorporate the design mechanisms used by the class.
Steps
Input Artifacts:
  • Analysis Class
  • Supplementary Specifications
  • Use-Case Realization
  • Design Class
Resulting Artifacts:
  • Design Class
  • Design Model
  • Use-Case Realization
Role: Designer

Workflow Details:
  • Analysis & Design
    • Grow the Design

Classes are the work-horses of the design effort. They actually perform the real work of the system. The other design elements: subsystems, packages and collaborations simply describe how classes are grouped or how they interoperate.

Capsules are also stereotyped classes, used to represent concurrent threads of execution in real-time systems. In such cases, other design classes are 'passive' classes, used within the execution context provided by the 'active' capsules. When the designer chooses not to use a design approach based on capsules, it is still possible to model concurrent behavior using 'active' classes.

Active classes are design classes which coordinate and drive the behavior of the passive classes - an active class is a class whose instances are active objects, owning their own thread of control.

Create Initial Design Classes To top of page

Start by creating one or several (initial) design classes for the analysis class given as input to this activity, and assign trace dependencies. The design classes created in this step will be refined, adjusted, split and/or merged in the subsequent steps when assigned various "design" properties, such as operations, methods, and a state machine, describing how the analysis class is designed.

Depending on the type of the analysis class (boundary, entity, or control) that is to be designed, there are specific strategies that can be used to create initial design classes.

Designing Boundary Classes

The general rule in analysis is that there will be one boundary class for each window, or one for each form, in the user interface. The consequence of this is that the responsibilities of the boundary classes can be on a fairly high level, and need then be refined and detailed in this step.

The design of boundary classes depends on the user interface (or GUI) development tools available to the project. Using current technology, it is quite common that the user interface is visually constructed directly in the development tool, thereby automatically creating user interface classes that need to be related to the design of control and/or entity classes. If the GUI development environment automatically creates the supporting classes it needs to implement the user interface, there is no need to consider them in design - only design what the development environment does not create for you.

Additional input to this work are sketches, or screen dumps from an executable user-interface prototype, that may have been created to further specify the requirements made on the boundary classes.

Boundary classes which represent the interfaces to existing systems are typically modeled as subsystems, since they often have complex internal behavior. If the interface behavior is simple (perhaps acting as only a pass-through to an existing API to the external system) one may choose to represent the interface with one or more design classes. If this route is chosen, use a single design class per protocol, interface, or API, and note special requirements about used standards and so on in the special requirements of the class.

Designing Entity Classes

During analysis, entity classes represent manipulated units of information; entity objects are often passive and persistent. In analysis, these entity classes may have been identified and associated with the analysis mechanism for persistence. Performance considerations may force some re-factoring of persistent classes, causing changes to the Design Model.

Designing Control Classes

A control object is responsible for managing the flow of a use case and thus coordinates most of its actions; control objects encapsulate logic that is not particularly related to user interface issues (boundary objects), or to data engineering issues (entity objects). This logic is sometimes called application logic , or business logic .

Given this, at least the following issues need to be taken into consideration when control classes are designed:

  • Complexity . Simple controlling or coordinating behavior can be handled by boundary and/or entity classes. As the complexity of the application grows, however, significant drawbacks to this approach surface:
  • The use case coordinating behavior becomes imbedded in the UI, making it more difficult to change the system.
  • The same UI cannot be used in different use case realizations without difficulty.
  • The UI becomes burdened with additional functionality, degrading its performance.
  • The entity objects may become burdened with use-case specific behavior, reducing their generality.

To avoid these problems, control classes are introduced to provide behavior related to coordinating flows-of-events

  • Change probability . If the probability of changing flows of events is low, or the cost is negligible, the extra expense and complexity of additional control classes may not be justified.
  • Distribution and performance . The need to run parts of the application on different nodes or in different process spaces introduces the need for specialization of design model elements. This specialization is often accomplished by adding control objects and distributing behavior from the boundary and entity classes onto the control classes. In doing this, the boundary classes migrate toward providing purely UI services, and the entity classes toward providing purely data services, with the control classes providing the rest.
  • Transaction management . Managing transactions is a classic coordination activity. When a framework to handle transaction management is absent, one would have one or more transaction manager classes which would interact to ensure that the integrity of transactions is maintained.

Identify Persistent Classes To top of page

Classes which need to be able to store their state on a permanent medium are referred to as 'persistent'. The need to store their state may be for permanent recording of class information, for back-up in case of system failure, or for exchange of information. A persistent class may have both persistent and transient instances; labeling a class 'persistent' means merely that some instances of the class may need to be persistent.

Example

The analysis mechanism for persistency might be realized by one of the following design mechanisms:

  • In-memory storage
  • Flash card
  • Binary file
  • Database Management System (DBMS)
  • depending on what is required by the class.

Note that persistent objects may not only be derived from entity classes; persistent objects may also be needed to handle non-functional requirements in general. Examples are persistent objects needed to maintain information relevant to process control, or to maintain state information between transactions.

Define Class Visibility To top of page

For each class, determine the class visibility within the package in which it resides. A 'public' class may be referenced outside the containing package. A 'private' class (or one whose visibility is 'implementation') may only be referenced by classes within the same package.

Define Operations To top of page

Identify Operations

To identify Operations on design classes:

  • Study the responsibilities of each corresponding analysis class, creating an operation for each responsibility. Use the description of the responsibility as the initial description of the operation.
  • Studying the use-case realizations in the class helps to see how the operations are used by the use-case realizations. Extend the operations, one use-case realization at the time, refining the operations, their descriptions, return types and parameters. Each use-case realization's requirements as regards classes are textually described in the Flow of Events of the use-case realization.
  • Study the use case Special Requirements, to make sure that you do not miss implicit requirements on the operation that might be stated there.

Operations are required to support the messages that appear on sequence diagrams because scripts; messages (temporary message specifications) which have not yet been assigned to operations describe the behavior the class is expected to perform. An example sequence diagram is shown below:

Messages form the basis for identifying operations.

Use-case realizations cannot provide enough information to identify all operations. To find the remaining operations, consider the following:

  • Is there a way to initialize a new instance of the class, including connecting it to instances of other classes to which it is associated?
  • Is there a need to test to see if two instances of the class are 'equal'?
  • Is there a need to create a copy of a class instance?
  • Are any operations required on the class by mechanisms which they use (example: a 'garbage collection' mechanism may require that an object be able to drop all of its references to all other objects in order for unused resources to be freed)?

Do not define operations which merely get and set the values of public attributes (see Define Attributes and Associations); these are generally generated by code generation facilities and do not need to be explicitly defined.

Name and Describe the Operations

For each operation, you should define the following:

  • The operation name . The name should be short and descriptive of the result the operation achieves.

    • The names of operations should follow the syntax of the implementation language. Example: find_location would be acceptable for C++ or Visual Basic, but not for Smalltalk (in which underscores are not used); a better name for all would be findLocation .
    • Avoid names that imply how the operation is performed (example: Employee.wages() is better than Employee.calculateWages() , since the latter implies a calculation is performed. The operation may simply return a value in a database).
    • The name of an operation should clearly show its purpose. Avoid unspecific names, such as getData , that are not descriptive about the result they return. Use a name that shows exactly what is expected, such as getAddress . Better yet, simply let the operation name be the name of the property which is returned or set; if it has a parameter, it sets the property, if it has no parameter it gets the property. Example: the operation address returns the address of a Customer , while address(aString) sets or changes the address of the Customer . The 'get' and 'set' nature of the operation are implicit from the signature of the operation.
    • Operations that are conceptually the same should have the same name even if different classes define them, they are implemented in entirely different ways, or they have a different number of parameters. An operation that creates an object, for example, should have the same name in all classes.
    • If operations in several classes have the same signature, the operation must return the same kind of result, appropriate for the receiver object. This is an example of the concept of polymorphism , which says that different objects should respond to the same message in similar ways. Example: the operation name should return the name of the object, regardless how the name is stored or derived. Following this principle makes the model easier to understand.


  • The return type . The return type should be the class of object that is returned by the operation.


  • A short description . As meaningful as we try to make it, the name of the operation is often only vaguely useful in trying to understand what the operation does. Give the operation a short description consisting of a couple of sentences, written from the operation user's perspective.


  • The parameters . For each parameter, create a short descriptive name, decide on its class, and give it a brief description. As you specify parameters, remember that fewer parameters mean better reusability. A small number of parameters makes the operation easier to understand and hence there is a higher likelihood of finding similar operations. You may need to divide an operation with many parameters into several operations. The operation must be understandable to those who want to use it. The brief description should include the following:

    • The meaning of the parameters (if not apparent from their names).
    • Whether the parameter is passed by value or by reference
    • Parameters which must have values supplied
    • Parameters which can be optional, and their default values if no value is provided
    • Valid ranges for parameters (if applicable)
    • What is done in the operation.
    • Which by reference parameters are changed by the operation.

Once you have defined the operations, complete the sequence diagrams with information about which operations are invoked for each message.

Define Operation Visibility

For each operation, identify the export visibility of the operation. The following choices exist:

  • Public : the operation is visible to model elements other than the class itself.
  • Implementation : the operation is visible only within to the class itself.
  • Protected : the operation is visible only to the class itself, to its subclasses, or to friends of the class (language dependent)
  • Private : the operation is only visible to the class itself and to friends of the class

Choose the most restricted visibility possible which can still accomplish the objectives of the operation. In order to do this, look at the sequence diagrams, and for each message determine whether the message is coming from a class outside the receiver's package (requires public visibility), from inside the package (requires implementation visibility), from a subclass (requires protected visibility) or from the class itself or a friend (requires private visibility).

Define Class Operations

For the most part, operations are 'instance' operations, that is, they are performed on instances of the class. In some cases, however, an operation applies to all instances of the class, and thus is a class-scope operation. The 'class' operation receiver is actually an instance of a metaclass , the description of the class itself, rather than any specific instance of the class. Examples of class operations include messages which create (instantiate) new instances, which return allInstances of a class, and so on.

To denote a class-scope operation, the operation string is underlined.

Define Methods To top of page

A method specifies the implementation of an operation. In many cases, methods are implemented directly in the programming language, in cases where the behavior required by the operation is sufficiently defined by the operation name, description and parameters. Where the implementation of an operation requires use of a specific algorithm, or requires more information than is presented in the operation's description, a separate method description is required. The method describes how the operation works, not just what it does.

The method, if described, should discuss:

  • How operations are to be implemented.
  • How attributes are to be implemented and used to implement operations.
  • How relationships are to be implemented and used to implement operations.

The requirements will naturally vary from case to case. However, the method specifications for a class should always state:

  • What is to be done according to the requirements?
  • What other objects and their operations are to be used?

More specific requirements may concern:

  • How parameters are to be implemented.
  • Any special algorithms to be used.

Sequence diagrams are an important source for this. From these it is clear what operations are used in other objects when an operation is performed. A specification of what operations are to be used in other objects is necessary for the full implementation of an operation. The production of a complete method specification thus requires that you identify the operations for the objects involved and inspect the corresponding sequence diagrams.

Define States To top of page

For some operations, the behavior of the operation depends upon the state the receiver object is in. A state machine is a tool for describing the states the object can assume and the events that cause the object to move from one state to another. State machines are most useful for describing active classes.

An example of a simple state machine is shown below:

A simple statechart diagram for a Fuel Dispenser

Each state transition event can be associated with an operation. Depending on the object's state, the operation may have a different behavior; the transition events describe how this occurs.

The method description for the associated operation should be updated with the state-specific information, indicating, for each relevant state, what the operation should do. States are often represented using attributes ; the statechart diagrams serve as input into the attribute identification step.

Define Attributes To top of page

During the definition of methods and the identification of states , attributes needed by the class and its operations are identified. Attributes provide information storage for the class instance, and are often used to represent the state of the class instance. Any information the class itself maintains is done through its attributes . For each attribute, define the following:

  • its name , which should follow the naming conventions of both the implementation language and the project;
  • its type , which will be an elementary data type supported by the implementation language;
  • its default or initial value , to which it is initialized when new instances of the class are created
  • its visibility , which will take one of the following values:
    • Public : the attribute is visible both inside and outside the package containing the class.
    • Protected : the attribute is visible only to the class itself, to its subclasses, or to friends of the class (language dependent)
    • Private : the attribute is only visible to the class itself and to friends of the class.
    • Implementation : the attribute is visible only to the class itself.
  • for persistent classes, whether the attribute is persistent (the default) or transient. Even though the class itself may be persistent, not all attributes of the class need to be persistent.

Check to make sure all attributes are needed. Attributes should be justified - it is easy for attributes to be added early in the process and survive long after they are no longer needed due to shortsightedness. Extra attributes, multiplied by thousands or millions of instances, can have a large effect on the performance and storage requirements of the system.

Refer to section "Attributes" in Guidelines: Design Class , for more information on attributes.

Define Dependencies To top of page

For each case where the communication between objects is required, ask the following questions:

  • Is the reference to the receiver passed as an parameter to the operation? If so, establish a dependency between the sender and receiver classes in a class diagram containing the two classes. In addition, if the collaboration diagram format for interactions is used, qualify the link visibility, setting it to ' parameter '.
  • Is the receiver a 'global'? If so, establish a dependency between the sender and receiver classes in a class diagram containing the two classes. In addition, if the collaboration diagram format for interactions is used, qualify the link visibility, setting it to ' global '.
  • Is the receiver a temporary object created and destroyed during the operation itself? If so, establish a dependency between the sender and receiver classes in a class diagram containing the two classes. In addition, if the collaboration diagram format for interactions is used, qualify the link visibility, setting it to ' local '.

Note that links modeled in this way are transient links, existing only for a limited duration, in the specific context of the collaboration - in that sense, they are instances of the association role in the collaboration. However, the relationship in a class model (i.e. independent of context) should be, as stated above, a dependency. As [RUM98] states, in the definition of transient link : "It is possible to model all such links as associations, but then the conditions on the associations must be stated very broadly, and they lose much of their precision in constraining combinations of objects". In this situation, the modeling of a dependency is less important than the modeling of the relationship in the collaboration, because the dependency does not describe the relationship completely, only that it exists.

Define Associations To top of page

Associations provide the mechanism for objects to communicate with one another. They provide objects with a "conduit" along which messages can flow. They also document the dependencies between classes, highlighting for us that changes in one class may be felt among many other classes.

Examine the method descriptions for each operation to understand how instances of the class communicate and collaborates with other objects. In order to send a message to another object, an object must have a reference to the receiver of the message. A collaboration diagram (an alternative representation of a sequence diagram) will show object communication in terms of links, as shown below:

Define Associations and Aggregations

The remaining messages will use either association or aggregation to specify the relationship between instances of two classes which communicate. For both of these associations, set the link visibility to ' field ' in collaboration diagrams. Other tasks include:

  • Establish the navigability of associations and aggregations. You do this by considering what navigabilities are required on their link instantiations in the interaction diagrams. Because navigability is true by default, you only need to find associations (and aggregations) where all opposite link roles of all objects of a class in the association do not require navigability. In those cases, set the navigability to false on the role of the class.
  • If there are attributes on the association itself (represented by "association classes"), create a design class to represent the 'association class', with the appropriate attributes. Interpose this class between the other two classes, and by establishing associations with appropriate multiplicity between the association class and the other two classes.
  • Specify if "association ends" should be ordered or not; this should be the case if the objects associated with an object at the other end of the association have an ordering that must be preserved.
  • If the associated (or aggregated) class is only referenced by the current class, and consider whether the class should be nested. Advantages of nesting classes include faster messaging and a simpler design model; disadvantages include having the space for the nested class statically allocated regardless whether there instances of the nested class, lack of object identity separate from the enclosing class, and inability to reference nested class instances from outside the enclosing class.

Associations and aggregations are best defined in a class diagram which depicts the associated classes. The class diagram should be owned by the package which contains the associated classes. An example class diagram, depicting associations and aggregations, is shown below:

Example Class Diagram, showing Associations, Aggregations, and Generalizations between Classes.

Handling Subscribe-Associations between Analysis Classes

Subscribe-associations between analysis classes are used to identify event dependencies between classes. In the Design Model we must explicitly handle these event dependencies, either using available event-handler frameworks, or by designing and building our own event-handler framework. In some programming languages such as Visual Basic this is straightforward by declaring, raising, and handling the corresponding events. In other languages you might have to use some additional library of reusable functions to handle subscriptions and events; if the functionality can't be purchased, it will need to be designed and built.

Define Generalizations To top of page

Classes may be organized into a generalization hierarchy to reflect common behavior and common structure. A common super-class can be defined, from which sub-classes can inherit both behavior and structure. Generalization is a notational convenience which allows us to define common structure and behavior in one place and re-use it where we find repeated behavior and structure. Refer to Guidelines: Generalization , for more information on generalization relationships.

When generalization is found, create a common super-class to contain the common attributes, associations, aggregations, and operations. Remove the common behavior from the classes which are to become sub-classes of the common super-class. Define a generalization relationship from the sub-class to the super-class.

Handle Non-Functional Requirements in General To top of page

The Design Classes should be refined to handle general non-functional requirements specific to the project. An important input to this step are the non-functional requirements on an analysis class that may already be stated in its special requirements and responsibilities. Such requirements are often specified in terms of what architectural (analysis) mechanisms are needed to realize the class; in this step the class is then refined to incorporate the design mechanisms corresponding to these analysis mechanisms.

For each design mechanism needed, qualify as many characteristics as possible, giving ranges where appropriate. Refer to Concepts: Analysis Mechanisms .

There can be several general design guidelines and mechanisms that need to be taken into consideration when classes are designed:

  • How to use existing products and components,
  • How to adapt to the programming language,
  • How to distribute objects,
  • How to achieve acceptable performance,
  • How to achieve certain security levels,
  • How to handle errors,
  • Etc.

Evaluate Your Results To top of page

You should check the design model at this stage to verify that your work is headed in the right direction. There is no need to review the model in detail, but you should consider the following check-points:

  • Checkpoints for the Design Model
  • Checkpoints for Design Classes