Class Diagrams

Every now and then I’m tasked to do something which requires me to quickly jog my memory. This time it was a class diagram, and Martin Fowler’s UML Distilled was so helpful that I’ve decided to list here some of the important points it raises, for future reference. I’ve added a few of my own notes where appropriate, and some guidelines from the excellent UML 2 And the Unified Process 2nd Edition and Visual Paradigm. I hope you may find these notes useful.

See Martin Fowler’s website.

For more information on class diagrams see wikipedia.

(I must be getting old, I remember when UML was taking over the world. Seems to have faded these days.)

Class Diagrams

A class diagram describes the types of objects in the system and the various kinds of static relationships that exist among them. Class diagrams also show the properties and operations of a class and the constraints that apply to the way objects are connected.

Purpose of Class Diagrams:

  1. Shows static structure of classifiers in a system
  2. Diagram provides a basic notation for other structure diagrams prescribed by UML
  3. Helpful for developers and other team members too
  4. Business Analysts can use class diagrams to model systems from a business perspective

A UML class diagram is made up of:

  • A set of classes and
  • A set of relationships between classes

Class

The UML uses the term “feature” as a general term that covers properties and operations of a class. The boxes which represent a class are divided into three compartments:

  1. The name of the class (in bold)
  2. Its attributes
  3. Its operations

A class is a description of a group of objects all with similar roles in the system, which consists of:

  • Structural features (attributes) define what objects of the class “know”
    • Represent the state of an object of the class
    • Are descriptions of the structural or static features of a class
  • Behavioral features (operations) define what objects of the class “can do”
    • Define the way in which objects may interact
    • Operations are descriptions of behavioral or dynamic features of a class

Hint: The biggest danger with class diagrams is that you can focus exclusively on structure and ignore behavior. Therefore, do them in conjunction with some form of behavioral technique.

When creating a class, it is important always to look at the class from the point of view of its potential clients. How will they see the class – is it too complex? Are any bits missing? Is it highly coupled to other classes? Does it do what they might expect from its name? These are important considerations and may be summarized in the following four minimal characteristics that a design class must have to be well formed:

  • complete and sufficient
  • primitive
  • high cohesion
  • low coupling

The public operations of a class define a contract between the class and clients of that class. It is important that this contract is clear, well defined, and acceptable to all parties. A complete and sufficient class gives the users of the class the contract they expect – no more and no less.

Operations should be designed to offer a single primitive, atomic service. A class should not offer multiple ways of doing the same thing as this is confusing to the clients and can lead to maintenance burdens and consistency problems. This is a good rule, but there are scenarios where it can be relaxed.

Each class should model a single abstract concept and should have a set of operations that support the intent of the class – this is cohesion. If a class needs to have many different responsibilities, you can create “helper” classes to implement some of these. The main class can then delegate responsibilities to its helpers. Cohesive classes are generally easy to understand, reuse, and maintain. A cohesive class has a small set of responsibilities that are all intimately related. Every operation, attribute, and association of the class is specifically designed to implement this small, focused set of responsibilities.

A particular class should be associated with just enough other classes to allow it to realize its responsibilities and you should only associate classes if there is a genuine semantic link between them – this is low coupling. A highly coupled object model is the equivalent of “spaghetti code” and is incomprehensible and unmaintainable.

During analysis and the early stages of design, the developer has two primary tasks:

  • Identify the classes that form the vocabulary of the problem domain
  • Invent the structures whereby sets of objects work together to provide behaviors that satisfy the requirements of the problem

Collectively, we call such classes and objects the key abstractions of the problem, and we call these cooperative structures the mechanisms of the implementation.

Objects have three responsibilities:

  1. What they know about themselves (properties)
  2. What they do (operations)
  3. What they know about other objects (relationships)

Properties

Properties represent structural features of a class as either an attribute or association. Typically, attributes are used for simple properties whilst associations are used for more complex types.

Hint: The choice is more about emphasis.

Visibility Options:

  • Public (+) Visible to any element that can see the class
  • Protected (#) Visible to other elements within the class and to subclasses
  • Private (-) Visible to other elements within the class
  • Package (~) Visible to elements within the same package

Attributes

Form:

visibility name: type multiplicity = default {property-string}

Example:

firstname: String[1] = “Untitled” {readonly}

Class Relationships

A class may be involved in one or more relationships with other classes.

A relationship can be one of the following types:

The names of relationships are written in the middle of the association line. Good relation names make sense when you read them out loud. Associations often have a small arrowhead to show the direction in which to read the relationship.

A role is a directional purpose of an association. Roles are written at the ends of an association line and describe the purpose played by that class in the relationship.

Associations

A simple association is a structural link between two peer classes.

Hetrosexual Monogamous Relationship

An association is a solid line between two classes, directed from the source class to the target class. The name of the property goes at the target end of the association, along with the multiplicity. The target end is the type of the property. Associations can show multiplicity at both ends of the line.

Example visibility of properties (attributes and associations):

Multiplicity

The multiplicity of a property is an indication of how many objects may fill the property. Typically:

  • 1 (An order must have exactly one customer)
  • 0..1 (A corporate customer may or may not have a single sales rep.)
  • * (A customer need not place an Order and there is no upper limit – zero or more)

If the lower and upper bounds are the same, you can use one number. * is short for 0..*

The default multiplicity for an attribute is 1.

Various terms for multiplicity:

  • Optional implies a lower bound of 0
  • Mandatory implies a lower bound if 1 or possibly more
  • Singlevalued implies an upper bound of 1
  • Multivalued implied an upper bound of more than 1: usually *

Hint: For a multivalued property, use a plural form for its name.

It is assumed the elements in a multivalued multiplicity form a set, in no particular order. Terms can be added to the association end to provide more clarity, for example:

{ordered}
{unordered}
{unique}
{nonunique}
{bag}

Hint: Object oriented design is about providing objects that have rich behavior. If you are making repeated calls for data by using accessors, that’s a sign that some behavior should be moved to the object that has the data. Craig Larman advises the same thing, see the Information Expert GRASP pattern. Also worth keeping in mind, pay attention to semantics, if you can’t name the class succinctly then it probably has too many responsibilities.

Bidirectional Associations

A bidirectional association is a pair of properties that are linked together as inverses, i.e. The Car class, owner:Person[1], and the Person class cars:Car[*]. Bidirectional associations have navigability arrows at both ends.

When implementing a bidirectional association let the single-valued side control the relationship. This means the slave end needs to leak the encapsulation of its data to the master end.

Operations

Form:

visibility name (parameter-list) : return-type {property-string}

The parameters are notated in a similar way to attributes:

direction name: type = default value

The direction indicates whether the parameter is input (in), output (out), or both (inout).

Example:

balanceOn (date: Date) : Money

Operations are the actions that a class knows how to carry out, typically implemented as methods in code. With conceptual models, use operations to indicate the principal responsibilities of that class, perhaps using a couple of words summarizing a CRC responsibility.

If the operation does not modify the observable state (what can be perceived from the outside), then include the property-string {query}. Try to write operations so that modifiers do not return a value, this is referred to as the Command-Query separation principle.

Hint: A subtle distinction is the difference between an operation and a method. An operation is something that is invoked on an object, the procedure declaration, where as a method is the body of a procedure.

the methods of a class should not depend in any way on the structure of any class, except the immediate (top-level) structure of their own class. Further, each method should send messages to objects belonging to a very limited set of classes only.

There are five main kinds of operations on an object. The three most common kinds are:

  • Modifier: an operation that alters the state of an object
  • Selector: an operation that accesses the state of an object but doe not alter the state
  • Iterator: an operation that permits all parts of an object to be accessed in some well-defined order

Two other kinds of operations are common; they represent the infrastructure necessary to create and destroy instances of a class:

  • Constructor: an operation that creates an object and/or initializes its state
  • Destructor: an operation that frees the state of an object and/or destroys the object itself

Collectively, all of the methods associated with a particular object comprise its protocol.

Responsibilities are meant to convey a sense of the purpose of an object and its place in the system. The responsibilities of an object are all the services it provides for all of the contracts it supports.

The state and behavior of an object collectively define the roles that an object may play in the world, which in turn fulfill the abstraction’s responsibilities.

Dependency

A dependency exists between two elements if changes to the definition of one element (the supplier or target) may cause changes to the other (the client or source).

You can add a keyword to a dependency to add more details of the relationship:

<<call>>The source calls an operation in the target
<<create>>The source creates instances of the target
<<derive>>The source is derived from the target
<<instantiate>>The source is an instance of the target
<<permit>>The target allows the source to access the target’s private features
<<realize>>The source is an implementation of a specification or interface defined by the target
<<refine>>Refinement indicates a relationship between different semantic levels
<<substitute>>The source is substitutable for the target
<<trace>>Used to track such things as requirements to classes
<<use>>The source requires the target for its implementation

You should keep dependencies to a minimum.

Hint: To understand and control dependencies, use them with a package diagram.

Constraints

A constraint is the expression of some semantic condition that must be preserved. It is an invariant of a class or relationship that must be preserved while the system is in a steady state. Preconditions and postconditions are examples of constraints that apply only when the system is in a steady state. We have already seen some examples of constraints such as {ordered}. The above diagram show how you might introduce custom constraints.

Hint: Constraints that consist of an expression are typically surrounded by braces { }.

Generalization

In OO languages this is known as inheritance.

Subclasses should always represent a “special kind of” rather than a “role played by” relationship.

Everything we say about the superclass is true also for the subclass.

Hint: An important principle of using inheritance effectively is substitutability (see SOLID)

Inheritance is a very powerful technique – it is a key mechanism for generating polymorphism in strongly typed languages. But it has certain undesirable characteristics:

  • It it the strongest form of coupling possible between two or more classes
  • Encapsulation is weak within a class hierarchy. Changes in the base class ripple down to change the subclass. This leads to what is known as the “fragile base class” problem, where changes to base classes have a large impact on other classes in the system
  • It is a very inflexible type of relationship

Hint: The Protected Variations GRASP pattern can help protect against brittle hierarchies.

Hint: See SOLID (Open/Closed principle) for protection against the fragile base class problem.

Hint: Prefer composition to inheritance, it is much more flexible, especially at runtime.

Try to avoid multiple inheritance

One common idiom to avoid multiple inheritance, but obtain the benefit of code reuse, is the mixin class. These are classes that are designed specifically to be “mixed in” with other classes.

Interface Realization

You only use inheritance when you are concerned about inheriting some implementation details (operations, attributes, relationships) from a superclass. When you just want to define a contract but are not concerned about inheriting implementation details then prefer interface realization, it is more flexible and robust than class inheritance.

Aggregation and Composition

Aggregation is a loose type of relationship between objects, an example might be a computer and its peripherals. Peripherals may come and go, may be shared between computers, and are not in any meaningful sense “owned” by any particular computer. Aggregation is type of whole-part relationship in which the aggregate is made up of many parts. As such, the whole tends to be the dominant controlling side of the relationship, whereas the part just tends to service requests from the whole and is therefore more passive. The aggregate can sometimes exist independently of the parts. The parts can exist independently of the aggregate. The aggregate is in some sense incomplete if some of the parts are missing. It is possible to have shared ownership of the parts by several aggregates.

Aggregation is transitive, and asymmetric. Many-to-one associations mean aggregation, provided there is no cycle in the aggregation graph.

Composition is a very strong type of relationship between objects, it is like a tree and its leaves. The tree is intimately related to its leaves. Leaves are owned by exactly one tree, they can’t be shared between trees, and when the tree dies the leaves go with it. Like aggregation, it is a whole-part relationship and is both transitive and asymmetric. The key difference between the two is that in composition each part belongs to at most one and only one whole.

Composition is depicted with a black diamond:

Composition is a strong form of aggregation. The composite has sole ownership and responsibility for its parts. The composite may release parts, provided responsibility for them is assumed by another object. If the composite is destroyed, it must either destroy all its parts, or give responsibility for them over to some other object. A part in a composite is equivalent to an attribute. One-to-one associations generally mean composition.

Templates

Templates (Generics) allow you to parametrize a type. This is a very powerful mechanism for reuse.

The template parameter names are local to a particular template. This means if two templates have a parameter called type, it is a different type in each case.

There is a variation on template syntax known as implicit binding. Here you don’t user an explicit <<bind>> realization to show template instantiation, but rather you bind implicitly by using a special syntax on the instantiated classes.

Nested Classes

A nested class is a class defined inside another class. They tend to be more concerned with how some functionality can be implemented rather than what the functionality is.

Reflexive Associations

These are objects within the same class that have a relationship with each other.

Final Notes

Design classes are the building blocks of the design model.

Design classes are classes whose specifications have been completed to such a degree that they can be implemented.

Design classes come from two sources: the problem domain and the solution domain. In the problem domain, they may be a refinement of analysis classes. It is also possible that one analysis class may become zero, one, or more design classes. In the solution domain they may be utility classes, middleware, reusable components etc.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s