Chapter 9. Advanced Classes

In this chapter

  • Classifiers, special properties of attributes and operations, and different kinds of classes

  • Modeling the semantics of a class

  • Choosing the right kind of classifier

Classes are indeed the most important building block of any object-oriented system. However, classes are just one kind of an even more general building block in the UML—classifiers. A classifier is a mechanism that describes structural and behavioral features. Classifiers include classes, interfaces, datatypes, signals, components, nodes, use cases, and subsystems.

Classifiers (and especially classes) have a number of advanced features beyond the simpler properties of attributes and operations described in the previous part: You can model multiplicity, visibility, signatures, polymorphism, and other characteristics. In the UML, you can model the semantics of a class so that you can state its meaning to whatever degree of formality you like.

In the UML, there are several kinds of classifiers and classes; it's important that you choose the one that best models your abstraction of the real world.

Getting Started

When you build a house, at some point in the project you'll make an architectural decision about your building materials. Early on, it's sufficient to simply state wood, stone, or steel. That's a level of detail sufficient for you to move forward. The material you choose will be affected by the requirements of your project—steel and concrete would be a good choice if you are building in an area susceptible to hurricanes, for example. As you move forward, the material you choose will affect your design decisions that follow—choosing wood versus steel will affect the mass that can be supported, for example.

As your project continues, you'll have to refine these basic design decisions and add more detail sufficient for a structural engineer to validate the safety of the design and for a builder to proceed with construction. For example, you might have to specify not just wood, but wood of a certain grade that's been treated for resistance to insects.

It's the same when you build software. Early in a project, it's sufficient to say that you'll include a Customer class that carries out certain responsibilities. As you refine your architecture and move to construction, you'll have to decide on a structure for the class (its attributes) and a behavior (its operations) that are sufficient and necessary to carry out those responsibilities. Finally, as you evolve to the executable system, you'll need to model details, such as the visibility of individual attributes and operations, the concurrency semantics of the class as a whole and its individual operations, and the interfaces the class realizes.

The UML provides a representation for a number of advanced properties, as Figure 9-1 shows. This notation permits you to visualize, specify, construct, and document a class to any level of detail you wish, even sufficient to support forward and reverse engineering of models and code.

Advanced Classes

Figure 9-1. Advanced Classes

Terms and Concepts

A classifier is a mechanism that describes structural and behavioral features. Classifiers include classes, associations, interfaces, datatypes, signals, components, nodes, use cases, and subsystems.

Classifiers

When you model, you'll discover abstractions that represent things in the real world and things in your solution. For example, if you are building a Web-based ordering system, the vocabulary of your project will likely include a Customer class (representing people who order products) and a Transaction class (an implementation artifact, representing an atomic action). In the deployed system, you might have a Pricing component, with instances living on every client node. Each of these abstractions will have instances; separating the essence and the instance of the things in your world is an important part of modeling.

Some things in the UML don't have instances—for example, packages and generalization relationships. In general, those modeling elements that can have instances are called classifiers. Even more important, a classifier has structural features (in the form of attributes) as well as behavioral features (in the form of operations). Every instance of a given classifier shares the same feature definitions, but each instance has its own value for each attribute.

The most important kind of classifier in the UML is the class. A class is a description of a set of objects that share the same attributes, operations, relationships, and semantics. Classes are not the only kind of classifier, however. The UML provides a number of other kinds of classifiers to help you model.

▪ Interface

A collection of operations that are used to specify a service of a class or a component

▪ Datatype

A type whose values are immutable, including primitive built-in types (such as numbers and strings) as well as enumeration types (such as Boolean)

▪ Association

A description of a set of links, each of which relates two or more objects.

▪ Signal

The specification of an asynchronous message communicated between instances

▪ Component

A modular part of a system that hides its implementation behind a set of external interfaces

▪ Node

A physical element that exists at run time and that represents a computational resource, generally having at least some memory and often processing capability

▪ Use case

A description of a set of a sequence of actions, including variants, that a system performs that yields an observable result of value to a particular actor

▪ Subsystem

A component that represents a major part of a system

For the most part, every kind of classifier may have both structural and behavioral features. Furthermore, when you model with any of these classifiers, you may use all the advanced features described in this chapter to provide the level of detail you need to capture the meaning of the abstraction.

Graphically, the UML distinguishes among these different classifiers, as Figure 9-2 shows.

Classifiers

Figure 9-2. Classifiers

Note

A minimalist approach would have used one icon for all classifiers; however, a distinctive visual cue was deemed important. Similarly, a maximal approach would have used different icons for each kind of classifier. That doesn't make sense either because, for example, classes and datatypes aren't that different. The design of the UML strikes a balance—some classifiers have their own icon, and others use special keywords (such as type, signal, and subsystem).

Visibility

One of the design details you can specify for an attribute or operation is visibility. The visibility of a feature specifies whether it can be used by other classifiers. In the UML, you can specify any of four levels of visibility.

1. public

Any outside classifier with visibility to the given classifier can use the feature; specified by prepending the symbol +.

2. protected

Any descendant of the classifier can use the feature; specified by prepending the symbol #.

3. private

Only the classifier itself can use the feature; specified by prepending the symbol -.

3. package

Only classifiers declared in the same package can use the feature; specified by prepending the symbol ~.

Figure 9-3 shows a mix of public, protected, and private figures for the class Toolbar.

Visibility

Figure 9-3. Visibility

When you specify the visibility of a classifier's features, you generally want to hide all its implementation details and expose only those features that are necessary to carry out the responsibilities of the abstraction. That's the very basis of information hiding, which is essential to building solid, resilient systems. If you don't explicitly adorn a feature with a visibility symbol, you can usually assume that it is public.

Note

The UML's visibility property matches the semantics common among most programming languages, including C++, Java, Ada, and Eiffel. Note that languages differ slightly in their semantics of visibility, however.

Instance and Static Scope

Another important detail you can specify for a classifier's attributes and operations is scope. The scope of a feature specifies whether each instance of the classifier has its own distinct value of the feature or whether there is just a single value of the feature shared by all instances of the classifier. In the UML, you can specify two kinds of owner scope.

1. instance

Each instance of the classifier holds its own value for the feature. This is the default and requires no additional notation.

2. static

There is just one value of the feature for all instances of the classifier. This has also been called class scope. This is notated by underlining the feature string.

As Figure 9-4 (a simplification of the first figure) shows, a feature that is static scoped is rendered by underlining the feature's name. No adornment means that the feature is instance scoped.

Owner Scope

Figure 9-4. Owner Scope

In general, most features of the classifiers you model will be instance scoped. The most common use of static scoped features is for private attributes that must be shared among all instances of a class, such as for generating unique IDs for new instances of a class.

Note

Static scope maps to what C++ and Java call static attributes and operations.

Static scope works somewhat differently for operations. An instance operation has an implicit parameter corresponding to the object being manipulated. A static operation has no such parameter; it behaves like a traditional global procedure that has no target object. Static operations are used for operations that create instances or operations that manipulate static attributes.

Abstract, Leaf, and Polymorphic Elements

You use generalization relationships to model a lattice of classes, with more-generalized abstractions at the top of the hierarchy and more-specific ones at the bottom. Within these hierarchies, it's common to specify that certain classes are abstract—meaning that they may not have any direct instances. In the UML, you specify that a class is abstract by writing its name in italics. For example, as Figure 9-5 shows, Icon, RectangularIcon, and ArbitraryIcon are all abstract classes. By contrast, a concrete class (such as Button and OKButton) may have direct instances.

Abstract and Concrete Classes and Operations

Figure 9-5. Abstract and Concrete Classes and Operations

Whenever you use a class, you'll probably want to inherit features from other, more-general classes, and have other, more-specific classes inherit features from it. These are the normal semantics you get from classes in the UML. However, you can also specify that a class may have no children. Such an element is called a leaf class and is specified in the UML by writing the property leaf below the class's name. For example, in the figure, OKButton is a leaf class, so it may have no children.

Operations have similar properties. Typically, an operation is polymorphic, which means that, in a hierarchy of classes, you can specify operations with the same signature at different points in the hierarchy. An operation in a child class overrides the behavior of a similar operation in the parent class. When a message is dispatched at run time, the operation in the hierarchy that is invoked is chosen polymorphically-that is, a match is determined at run time according to the type of the object. For example, display and isInside are both polymorphic operations. Furthermore, the operation Icon::display() is abstract, meaning that it is incomplete and requires a child to supply an implementation of the operation. In the UML, you specify an abstract operation by writing its name in italics, just as you do for a class. By contrast, Icon::getID() is a leaf operation, so designated by the property leaf. This means that the operation is not polymorphic and may not be overridden. (This is similar to a Java final operation.)

Note

Abstract operations map to what C++ calls pure virtual operations; leaf operations in the UML map to C++ nonvirtual operations.

Multiplicity

Whenever you use a class, it's reasonable to assume that there may be any number of instances of that class (unless, of course, it is an abstract class and may not have any direct instances, although there may be any number of instances of its concrete children). Sometimes, though, you'll want to restrict the number of instances a class may have. Most often, you'll want to specify zero instances (in which case, the class is a utility class that exposes only static-scoped attributes and operations), one instance (a singleton class), a specific number of instances, or many instances (the default case).

The number of instances a class may have is called its multiplicity. Multiplicity is a specification of the range of allowable cardinalities an entity may assume. In the UML, you can specify the multiplicity of a class by writing a multiplicity expression in the upper-right corner of the class icon. For example, in Figure 9-6, NetworkController is a singleton class. Similarly, there are exactly three instances of the class ControlRod in the system. Multiplicity applies to attributes, as well. You can specify the multiplicity of an attribute by writing a suitable expression in brackets just after the attribute name. For example, in the figure, there are two or more consolePort instances in the instance of NetworkController.

Multiplicity

Figure 9-6. Multiplicity

Note

The multiplicity of a class applies within a given context. There is an implied context for the entire system at the top level. The entire system can be regarded as a structured classifier.

Attributes

At the most abstract level, when you model a class's structural features (that is, its attributes), you simply write each attribute's name. That's usually enough information for the average reader to understand the intent of your model. As the previous parts have described, however, you can also specify the visibility, scope, and multiplicity of each attribute. There's still more. You can also specify the type, initial value, and changeability of each attribute.

In its full form, the syntax of an attribute in the UML is

[visibility] name
[':' type] ['[' multiplicity] ']']
['=' initial-value]
[property-string {',' property-string}]

For example, the following are all legal attribute declarations:

origin

Name only

+ origin

Visibility and name

origin : Point

Name and type

name : String[0..1]

Name, type, and multiplicity

origin : Point = (0,0)

Name, type, and initial value

id: Integer {readonly}

Name and property

Unless otherwise specified, attributes are always changeable. You can use the readonly property to indicate that the attribute's value may not be changed after the object is initialized.

You'll mainly want to use readonly when modeling constants or attributes that are initialized at the creation of an instance and not changed thereafter.

Note

The readonly property maps to const in C++.

Operations

At the most abstract level, when you model a class's behavioral features (that is, its operations and its signals), you will simply write each operation's name. That's usually enough information for the average reader to understand the intent of your model. As the previous parts have described, however, you can also specify the visibility and scope of each operation. There's still more: You can also specify the parameters, return type, concurrency semantics, and other properties of each operation. Collectively, the name of an operation plus its parameters (including its return type, if any) is called the operation's signature.

Note

The UML distinguishes between operation and method. An operation specifies a service that can be requested from any object of the class to affect behavior; a method is an implementation of an operation. Every nonabstract operation of a class must have a method, which supplies an executable algorithm as a body (generally designated in some programming language or structured text). In an inheritance lattice, there may be many methods for the same operation, and polymorphism selects which method in the hierarchy is dispatched during run time.

In its full form, the syntax of an operation in the UML is

[visibility] name ['(' parameter-list ')']
[':' return-type]
[property-string {',' property-string}]

For example, the following are all legal operation declarations:

display

Name only

+ display

Visibility and name

set(n : Name, s : String)

Name and parameters

getID() : Integer

Name and return type

restart() {guarded}

Name and property

In an operation's signature, you may provide zero or more parameters, each of which follows the syntax

[direction] name : type [= default-value]

Direction may be any of the following values:

in

An input parameter; may not be modified

out

An output parameter; may be modified to communicate information to the caller

inout

An input parameter; may be modified to communicate information to the caller

Note

An out or inout parameter is equivalent to a return parameter and an in parameter. Out and inout are provided for compatibility with older programming languages. Use explicit return parameters instead.

In addition to the leaf and abstract properties described earlier, there are defined properties that you can use with operations.

1. query

Execution of the operation leaves the state of the system unchanged. In other words, the operation is a pure function that has no side effects.

2. sequential

Callers must coordinate outside the object so that only one flow is in the object at a time. In the presence of multiple flows of control, the semantics and integrity of the object cannot be guaranteed.

3. guarded

The semantics and integrity of the object is guaranteed in the presence of multiple flows of control by sequentializing all calls to all of the object's guarded operations. In effect, exactly one operation at a time can be invoked on the object, reducing this to sequential semantics.

4. concurrent

The semantics and integrity of the object is guaranteed in the presence of multiple flows of control by treating the operation as atomic. Multiple calls from concurrent flows of control may occur simultaneously to one object on any concurrent operation, and all may proceed concurrently with correct semantics; concurrent operations must be designed so that they perform correctly in case of a concurrent sequential or guarded operation on the same object.

5. static

The operation does not have an implicit parameter for the target object; it behaves like a traditional global procedure.

The concurrency properties (sequential, guarded, concurrent) address the concurrency semantics of an operation, properties that are relevant only in the presence of active objects, processes, or threads.

Template Classes

A template is a parameterized element. In such languages as C++ and Ada, you can write template classes, each of which defines a family of classes. (You can also write template functions, each of which defines a family of functions.) A template may include slots for classes, objects, and values, and these slots serve as the template's parameters. You can't use a template directly; you have to instantiate it first. Instantiation involves binding these formal template parameters to actual ones. For a template class, the result is a concrete class that can be used just like any ordinary class.

The most common use of template classes is to specify containers that can be instantiated for specific elements, making them type-safe. For example, the following C++ code fragment declares a parameterized Map class.

template<class Item, class VType, int Buckets>
class Map {
public:
 virtual map(const Item&, const VType&);
 virtual Boolean isMappen(const Item&) const;
 ...
};

You might then instantiate this template to map Customer objects to Order objects.

m : Map<Customer, Order, 3>;

You can model template classes in the UML as well. As Figure 9-7 shows, you render a template class just as you do an ordinary class, but with an additional dashed box in the upper-right corner of the class icon, which lists the template parameters.

Template Classes

Figure 9-7. Template Classes

As the figure goes on to show, you can model the instantiation of a template class in two ways. First, you can do so implicitly, by declaring a class whose name provides the binding. Second, you can do so explicitly, by using a dependency stereotyped as bind, which specifies that the source instantiates the target template using the actual parameters.

Standard Elements

All of the UML's extensibility mechanisms apply to classes. Most often, you'll use tagged values to extend class properties (such as specifying the version of a class) and stereotypes to specify new kinds of components (such as model- specific components).

The UML defines four standard stereotypes that apply to classes.

1. metaclass

Specifies a classifier whose objects are all classes

2. powertype

Specifies a classifier whose objects are classes that are the children of a given parent class

3. stereotype

Specifies that the classifier is a stereotype that may be applied to other elements

4. utility

Specifies a class whose attributes and operations are all static scoped

Note

A number of other standard stereotypes or keywords that apply to classes are discussed elsewhere.

Common Modeling Techniques

Modeling the Semantics of a Class

The most common purpose for which you'll use classes is to model abstractions that are drawn from the problem you are trying to solve or from the technology you are using to implement a solution to that problem. Once you've identified those abstractions, the next thing you'll need to do is specify their semantics.

In the UML, you have a wide spectrum of modeling possibilities at your disposal, ranging from the very informal (responsibilities) to the very formal (OCL—Object Constraint Language). Given these choices, you must decide the level of detail that is appropriate to communicate the intent of your model. If the purpose of your model is to communicate with end users and domain experts, you'll tend to lean toward the less formal. If the purpose of your model is to support round-trip engineering, which flows between models and code, you'll tend to lean toward the more formal. If the purpose of your model is to rigorously and mathematically reason about your models and prove their correctness, you'll lean toward the very formal.

Note

Less formal does not mean less accurate. It means less complete and less detailed. Pragmatically, you'll want to strike a balance between informal and very formal. This means providing enough detail to support the creation of executable artifacts, but still hiding those details so that you do not overwhelm the reader of your models.

To model the semantics of a class, choose among the following possibilities, arranged from informal to formal.

  • Specify the responsibilities of the class. A responsibility is a contract or obligation of a type or class and is rendered in a note attached to the class, or in an extra compartment in the class icon.

  • Specify the semantics of the class as a whole using structured text, rendered in a note (stereotyped as semantics) attached to the class.

  • Specify the body of each method using structured text or a programming language, rendered in a note attached to the operation by a dependency relationship.

  • Specify the pre- and postconditions of each operation, plus the invariants of the class as a whole, using structured text. These elements are rendered in notes (stereotyped as precondition, postcondition, and invariant) attached to the operation or class by a dependency relationship.

  • Specify a state machine for the class. A state machine is a behavior that specifies the sequences of states an object goes through during its lifetime in response to events, together with its responses to those events.

  • Specify internal structure of the class.

  • Specify a collaboration that represents the class. A collaboration is a society of roles and other elements that work together to provide some cooperative behavior that's bigger than the sum of all the elements. A collaboration has a structural part as well as a dynamic part, so you can use collaborations to specify all dimensions of a class's semantics.

  • Specify the pre- and postconditions of each operation, plus the invariants of the class as a whole, using a formal language such as OCL.

Pragmatically, you'll end up doing some combination of these approaches for the different abstractions in your system.

Note

When you specify the semantics of a class, keep in mind whether your intent is to specify what the class does or how it does it. Specifying the semantics of what a class does represents its public, outside view; specifying the semantics of how a class does it represents its private, inside view. You'll want to use a mixture of these two views, emphasizing the outside view for clients of the class and emphasizing the inside view for those who implement the class.

Hints and Tips

When you model classifiers in the UML, remember that there is a wide range of building blocks at your disposal, from interfaces to classes to components, and so on. You must choose the one that best fits your abstraction. A well-structured classifier

  • Has both structural and behavioral aspects.

  • Is tightly cohesive and loosely coupled.

  • Exposes only those features necessary for clients to use the class and hides all others.

  • Is unambiguous in its intent and semantics.

  • Is not so overly specified that it eliminates all degrees of freedom for its implementers.

  • Is not so underspecified that it renders the meaning of the classifier as ambiguous.

When you draw a classifier in the UML,

  • Show only those properties of the classifier that are important to understand the abstraction in its context.

  • Chose a stereotyped version that provides the best visual cue to the intent of the classifier.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset