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, you must 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 Guideline: 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 Guideline: Sequence Diagram and Guideline: 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, you must 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 Concept: Structured Class.
|