Guidelines: Design Class
Topics
A design class represents
an abstraction of one or several classes in the system's implementation; exactly
what it corresponds to depends on the implementation language. For example,
in an object-oriented language such as C++, a class can correspond to a plain
class. Or in Ada, a class can correspond to a tagged type defined in the visible
part of a package.
Classes define objects, which in turn realize (implement)
the use cases. A class originates from the requirements the use-case
realizations make on the objects needed in the system, as well as from any
previously developed object model.
Whether or not a class is good depends heavily on the implementation
environment. The proper size of the class and its objects depends on the
programming language, for example. What is considered right when using Ada might
be wrong when using Smalltalk. Classes should map to a particular phenomenon in
the implementation language, and the classes should be structured so that the
mapping results in good code.
Even though the peculiarities of the implementation language influence the
design model, keep the class structure easy to understand and modify.
You should design as if you had classes and encapsulation even if the
implementation language does not support this.
The only way other objects can get access to or affect the attributes or
relationships of an object is through its operations. The
operations of an object are defined by its class. A specific behavior can be
performed via the operations, which may affect the attributes and relationships
the object holds and cause other operations to be performed. An operation
corresponds to a member function in C++ or to a function or procedure in Ada.
What behavior you assign to an object depends on what role it has in the
use-case realizations.
In the specification of an operation, the parameters constitute formal
parameters. Each parameter has a name and type. You can use the
implementation language syntax and semantics to specify the operations and their
parameters so that they will already be specified in the implementation language
when coding starts.
Example:
In the Recycling Machine System, the objects
of a Receipt Basis class keep track of how many deposit items
of a certain type a customer has handed in. The behavior of a Receipt
Basis object includes incrementing the number of objects returned. The
operation insertItem, which receives a reference to the item
handed in, fills this purpose.
Use the implementation language syntax and semantics when
specifying operations.
An operation nearly always denotes object behavior. An operation can also
denote behavior of a class, in which case it is a class operation.
This can be modeled in the UML by type-scoping the operation.
The following visibilities are possible on an operation:
- Public: the operation is visible to model elements other
than 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
- Implementation: the operation is visible only within to
the class itself.
Public visibility should be used very sparingly,
only when an operation is needed by another class.
Protected visibility should be the default;
it protects the operation from use by external classes, which promotes loose
coupling and encapsulation of behavior.
Private visibility should be used in cases where you want to
prevent subclasses from inheriting the operation. This provides
a way to de-couple subclasses from the super-class and to reduce the need to
remove or exclude unused inherited operations.
Implementation visibility is the most restrictive;
it is used in cases where only the class itself is able to use the operation. It
is a variant of Private visibility, which for most cases is
suitable.
An object can react differently to a specific message depending on what state
it is in; the state-dependent behavior of an object is defined by an associated
statechart diagram. For each state the object can enter, the statechart diagram
describes what messages it can receive, what operations will be carried out, and
what state the object will be in thereafter. Refer to Guidelines:
Statechart Diagram for more information.
A collaboration is a dynamic set of object interactions in which a set of
objects communicate by sending messages to each other. Sending
a message is straightforward in Smalltalk; in Ada it is done as a subprogram
call. A message is sent to a receiving object that invokes an operation within
the object. The message indicates the name of the operation to perform, along
with the required parameters. When messages are sent, actual parameters
(values for the formal parameters) are supplied for all the parameters.
The message transmissions among objects in a use-case realization and the
focus of control the objects follow as the operations are invoked are described
in interaction diagrams. See Guidelines: Sequence Diagram
and Guidelines: Communication Diagram for information
about these diagrams.
An attribute is a named property of an object. The attribute name is a noun
that describes the attribute's role in relation to the object. An attribute can
have an initial value when the object is created.
You should model attributes only if doing so makes an object more
understandable. You should model the property of an object as an attribute only
if it is a property of that object alone. Otherwise, you should
model the property with an association or aggregation relationship to a class
whose objects represent the property.
Example:
An example of how an attribute is modeled. Each member of a
family has a name and an address. Here, we have identified the attributes my
name and home address of type Name
and Address, respectively:
In this example, an association is used instead of an
attribute. The my name property is probably unique to each
member of a family. Therefore we can model it as an attribute of the attribute
type Name. An address, though, is shared by all family members,
so it is best modeled by an association between the Family Member
class and the Address class.
It is not always easy to decide immediately whether to model some concept as
a separate object or as an attribute of another object. Having unnecessary
objects in the object model leads to unnecessary documentation and development
overhead. You must therefore establish certain criteria to determine how
important a concept is to the system.
- Accessibility. What governs your choice of object versus
attribute is not the importance of the concept in real life, but the need to
access it during the use case. If the unit is accessed frequently, model it
as an object.
- Separateness during execution. Model concepts handled
separately during the execution of use cases as objects.
- Ties to other concepts. Model concepts strictly tied to
certain other concepts and never used separately, but always via an object,
as an attribute of the object.
- Demands from relationships. If, for some reason,
relate a unit from two directions, re-examine the unit to see if it should
be a separate object. Two objects cannot associate the same instance of an
attribute type.
- Frequency of occurrence. If a unit exists only during a
use case, do not model it as an object. Instead model it as an attribute to
the object that performs the behavior in question, or simply mention it in
the description of the affected object.
- Complexity. If an object becomes too complicated because
of its attributes, you may be able to extract some of the attributes into
separate objects. Do this in moderation, however, so that you do not have
too many objects. On the other hand, the units may be very straightforward.
For example, classified as attributes are (1) units that are simple enough
to be supported directly by primitive types in the implementation language,
such as, integers in C++, and (2) units that are simple enough to be
implemented by using the application-independent components of the
implementation environment, such as, String in C++ and
Smalltalk-80.
You will probably model a concept differently for different systems. In one
system, the concept may be so vital that you will model it as an object. In
another, it may be of minor importance, and you will model it as an attribute of
an object.
Example:
For example, for an airline company you would develop a
system that supports departures.
A system that supports departures. Suppose the personnel
at an airport want a system that supports departures. For each departure, you
must define the time of departure, the airline, and the destination. You can
model this as an object of a class Departure, with the
attributes time of departure, airline, and destination.
If, instead, the system is developed for a travel agency, the
situation might be somewhat different.
Flight destinations forms its own object, Destination.
The time of departure, airline, and destination will, of
course, still be needed. Yet there are other requirements, because a travel
agency is interested in finding a departure with a specific destination. You
must therefore create a separate object for Destination. The
objects of Departure and Destination must, of
course, be aware of each other, which is enabled by an association between their
classes.
The argument for the importance of certain concepts is also valid for
determining what attributes should be defined in a class. The class Car
will no doubt define different attributes if its objects are part of a
motor-vehicle registration system than if its objects are part of an automobile
manufacturing system.
Finally, the rules for what to represent as objects and what to represent as
attributes are not absolute. Theoretically, you can model everything as objects,
but this is cumbersome. A simple rule of thumb is to view an object as something
that at some stage is used irrespective of other objects. In addition, you do
not have to model every object property using an attribute, only properties
necessary to understand the object. You should not model details that are so
implementation-specific that they are better handled by the implementer.
Class
Attributes
An attribute nearly always denotes object properties. An attribute can also
denote properties of a class, in which case it is a class attribute.
This can be modeled in the UML by type-scoping the attribute.
An object can encapsulate something whose value can change without the object
performing any behavior. It might be something that is really an external unit,
but that was not modeled as an actor. For example, system boundaries may have
been chosen so that some form of sensor equipment lies within them. The sensor
can then be encapsulated within an object, so that the value it measures
constitutes an attribute. This value can then change continually, or at certain
intervals without the object being influenced by any other object in the system.
Example:
You can model a thermometer as an object; the object has an
attribute that represents temperature, and changes value in response to changes
in the temperature of the environment. Other objects may ask for the current
temperature by performing an operation on the thermometer object.
The value of the attribute temperature
changes spontaneously in the Thermometer object.
You can still model an encapsulated value that changes in this way as an
ordinary attribute, but you should describe in the object's class that it
changes spontaneously.
Attribute visibility assumes 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 to the class
itself.
Public visibility should be used very sparingly,
only when an attribute is directly accessible by another class. Defining public
visibility is effectively a short-hand notation for defining the attribute
visibility as protected, private or implementation, with associated public
operations to get and set the attribute value. Public attribute visibility can
be used as a declaration to a code generator that these get/set operations
should be automatically generated, saving time during class definition.
Protected visibility should be the default;
it protects the attribute from use by external classes, which promotes loose
coupling and encapsulation of behavior.
Private visibility should be used in cases where you want to
prevent subclasses from inheriting the attribute. This provides
a way to de-couple subclasses from the super-class and to reduce the need to
remove or exclude unused inherited attributes.
Implementation visibility is the most restrictive;
it is used in cases where only the class itself is able to use the attribute. It
is a variant of Private visibility, which for most cases is
suitable.
Some classes may represent complex abstractions and have a complex structure.
While modeling a class, the designer may want to represent its internal participating
elements and their relationships, to make sure that the implementer will accordingly
implement the collaborations happening inside that class.
In UML 2.0, classes are defined as structured
classes, with the capability to have a internal structure and ports. Then,
classes may be decomposed into collections of connected parts that may be further
decomposed in turn. A class may be encapsulated by forcing communications from
outside to pass through ports obeying declared interfaces.
Thus, in addition to using class diagrams to represent class relationships
(e.g. associations, compositions and aggregations) and attributes, the designer
may want to use a composite structure diagram. This diagram provides the designer
a mechanism to show how instances of internal parts play their roles within
an instance of a given class.
For more information on this topic and examples on composite structure diagram,
see Concepts: Structured Class.
|