OOP and UML
The purpose of this document is to provide you with information about UML and how to use it.
What is UML?
UML, Unified Modeling Language, is a standard notation for the modeling of real-world objects as a first step in developing an object oriented program. It describes one consistent language for specifying, visualizing, constructing and documenting the artifacts of software systems.
Developing a model for a software system before you actually start programming the software, can be seen as having a blueprint for a large building you want to build. It is essential to have one. Although this doesn’t mean that you have to draw a model each time a simple class is introduced in your software. You have to think for yourself whether you want a model or not.
A few notation-rules
Graphs and their contents
Most UML diagrams are graphs containing nodes connected by paths. The information is mostly in the typology, not in the size or placement of the symbols. There are three kinds of visual relationships that are important:
Connection (usually lines)
- Containment (2D shapes with boundaries)
- Visual attachment (one object being near another)
UML notation is intended to be drawn on a 2D surface.
There are basically four kinds of graphical constructs used in the UML notation:
Icons – an icon is a graphical figure of a fixed size and shape. It does not expand to hold contents. Icons may appear within area symbols, as terminators on paths or as standalone symbols that may or may not be connected to paths
2D symbols – Two dimensional symbols have variable length and width and they can expand to hold other things, such as lists, strings or other symbols. Many of them are divided into compartments of similar or different kinds. Dragging or deleting a 2D-symbol affects its contents and any paths connected to it.
Paths – Sequences of line segments whose endpoints are attached. Conceptually, a path is a single topological entity, although its segments may be manipulated graphically. A segment may not exist apart from his path. Paths are always attached to other graphic symbols at both ends (no dangling lines). Paths may have terminators, which are icons that appear in some sequence on the end of the path and that qualify the meaning of the path symbol.
Strings – Present various kinds of information in an "unparsed" form. UML assumes that each usage of a string in the notation has a syntax by which it can be parsed into underlying model information. For example, syntaxes are given for attributes, operations and transitions. Strings may exist as singular elements of symbols or compartments of symbols, as elements in a list as labels attached to symbols or paths, or as stand-alone elements in a diagram.
Attributes and behavior
Each object has various attributes. An attribute is a name/value pair. For example, my age is 22. The attribute name is "age", the value is "22". Objects also have behavior. I can stand, walk, sleep etc.
There are different kinds of relationships:
Dependency is where one object must know about another.
In a simple dependency relationship, all we know is that one object has knowledge of another. For example: if one class requires the inclusions of the header for another, that establishes a dependency.
We draw a dependency using a dashed arrow. If a depends on b be sure the arrow points at b.
In association, the state of the object depends on another object.
In an association we say that, as part of understanding the state of one object, you must understand the relationship with the second object. There are many types of association which model real-world relationships, such as owns (Arjen owns this bike), works for (Raymond works for Harry) and so forth.
In an association the two objects have a strong connection, but neither one is a part of the other. The relationship is stronger that dependency; there is an association affecting both sides of the relationship.
An association between a and b is shown by a line joining the two classes:
If there is no arrow on the line, the association is taken to be bi-directional. A unidirectional association is indicated like this:
To improve the clarity of a class diagram, the association between two objects may be named:
Aggegration models the whole/part relation
Objects are often made up of other objects. Cars are made up of steering wheels, engines, transmissions and so forth. Each of these components may be an object in its own right. The special association of a car to its compoment parts is known as aggegration.
An aggregation relationship is indicated by placing a white diamond at the end of the association next to the aggregate class. If b aggregates a, then a is a part of b, but their lifetimes are independent:
Composition models a relationship in which one object is an integral part of another.
Often, the component parts of an object spring into existence only with the entire object. For example, a person may consist of a number of parts including the heart, lungs etc. If you were modeling a person, the lifetime of the heart and lungs would be directly controlled by the lifetime of the aggregating person.We call this special relationship composition.
In aggregation, the parts may live independently. While my car consists of its wheels and tires and radio, each of those components may have existed before the car was created. In composition, the lifetime of the contained object is tied to the lifetime of the containing object.
Composition is shown by a black diamond on the end of association next to the composite class. If b is composed of a, then b controls the lifetime of a.
Inheritance is a specialization/generalization relationship between objects.
We (humans) have inherited the ability to create categories based on the behavior and characteristics of the things in our environment. This is best shown with an example: If something breathes and moves, we say it’s an animal. If one of those things that move and breathe also has live young and nurses them, we say it’s a mammal. We know that mammals are kinds of animals, and so we can predict that if we see a mammal, it will in all likelihood breathe and move around.
If a mammal barks and wags its tail, we say it’s a dog. If it won’t stop barking and runs about our feet demanding attention, we figure it’s a terrier. Each of these classifications gives us additional information. When we’re done, we have created a hierarchy of types.
Some animals are mammals and some are reptiles. Some mammals are dogs and some are horses. Each type will share certain characteristics, and that helps us understand them and predict their behavior and attributes.
There is only one right way to draw this:
Once we have this categorization, we can see that reading up the animal hierarchy reveals the generalization of shared characteristics.
In the same way, we could create a model of a car. To do so, we must ask ourself some questions:
What is a car? What makes a car different from a truck, from a person, from a rock? One of the delights of object oriented programming is that these questions become relevant to us; understanding how we perceive and think about the objects in the real world directly relates to how we design these objects in our model.
From one perspective, a car is the sum of its parts: steering wheel, brakes, seats, headlights. Here, we are thinking in terms of aggregation. From a second perspective, one that is equally true, a car is a type of vehicle.
Because a car is a vehicle, it moves and carries things. That is the essence of being a vehicle. Cars inherit the characteristics moves and carries things from their "parent" type, which is "vehicle".
We also know that car specializes vehicles. They are a special kind of vehicle, one which meets the federal specifications for automobiles.
We can model this relationship with inheritance. We say that the car type inherits publicly from the vehicle-type; that a car is-a vehicle.
Public inheritance establishes a is-a relationship. It creates a parent class (vehicle) and a derived class (car) and implies that the car is a specialization of the type vehicle. Everything true about a vehicle should be true about a car, but the converse is not true. The car may specialize how it moves, but it ought to move.
What is a motor vehicle? This is a different specialization, at a different level of abstraction. A motor vehicle is any vehicle driven by a motor. A car is one such type, a truck is another. We can model these more complex relationships with inheritance as well.
Which model is better? Depends on what you’re modeling! How do you decide which model you want to use? Ask yourself questions. Is there something about "Motor Vehicle" I want to model? Am I going to model other, non-motorized vehicles? If you do, you should use the second model. To show this with an example: Suppose you want to create two classes for vehicles which are horse-drawn.
A critical aspect of public inheritance is that it should model specialization/generalization, and nothing else! If you want to inherit implementation, but are not establishing an is-a relationship, you should use private inheritance.
Private inheritance establishes an implemented-in-terms-of rather than an is-a relationship.
One of the capabilities available in C++, is multiple inheritance. Multiple inheritance allows a class to inherit from more than one base class, bringing in the members and methods of two or more classes.
In simple multiple inheritance, the two base classes are unrelated. And example of multiple inheritance is shown below. Also notice how the functions are displayed in this model.
In this rather simple model, the Griffin class inherits from both Lion and Eagle. This means a Griffin can eatMeat(), roar(), squawk() and fly(). A problem arises when both Lion and Eagle share a common base class, for example Animal.
This common base class, Animal, may have methods of member variables which Griffin will now inherit twice. When you call Griffin’s Sleep() method, the compiler will not know which Sleep() you wish to invoke. As the designer of the Griffin class, you must remain aware of these relationships and be prepared to solve the ambiguities they create. C++ facilitates this by providing virtual inheritance.
Without virtual inheritance
With virtual inheritance
With virtual inheritance, Griffin inherits just one copy of the members of Animal, and the ambiguity is solved. The problem is that both Lion and Eagle classes must know that they may be involved in a multiple inheritance relationship; the virtual keyword must be on their declaration of inheritance, not that of Griffin.
Using multiple inheritance when you need aggregation
How do you know when to use multiple inheritance and when to avoid it? Should a car inherit from steering wheel, tire and doors? Should a police car inherit from municipal property and vehicle?
The first guideline is that public inheritance should always model specialization. The common expression for this is that inheritance should model is-a relationships and aggegration should model has-a relationships.
Is a car a steering wheel? Clearly not. You might argue that a car is a combination of a steering wheel, a tire and a set of doors, but this is not modeled in inheritance. A car is not a specialization of these things; it’s an aggregation of these things. A car has a steering wheel, it has doors and it has tires. Another good reason why you should not inherit car from door is the fact that a car usually has more than one door. This is not a relationship that can be modeled with inheritance.
Is a police car both a vehicle and a municipal property? Clearly it is both. In fact, it specializes both. As such, multiple inheritance makes a lot of sense here:
Base classes and derived classes
Derived classes should know who their base class is, and they depend on their base classes. Base classes, on the other hand, should know nothing about their derived classes. Do not put the header for derived classes into your base class files.
You want to be very suspicious of any design that calls for casting down the inheritance hierarchy. You cast down when you ask a pointer for it’s "real" (run-time) class and then cast that pointer to the derived type. In theory, base pointers should be polymorphic, and figuring out the "real" type of the pointer and calling the "right" method should be left to the compiler.
The most common use of casting down is to invoke a method that doesn’t exist in the base class. The question you should be asking yourself is why you are in a situation where you need to do this. If knowledge of the run-time type is supposed to be hidden, then why are you casting down?
Single instance classes
You also want to be very aware of derived classes for which there is always only one instance. Don’t confuse it with a singleton, in which the application only needs a single instance of a type, for example only one document or only one database.
Drawing a class
Suppose you want to create a class CFloatPoint, which has two members: x and y, which are both of type ‘float’, and a function "Empty()" which resets both members to value 0.00000.
First of all, you draw the class itself:
Now, we want the members x and y to be visible in the model:
As you can see, x and y are both private (lock-sign) and have the type "float".
Now, we want to make the function Empty() visible in the model:
Suppose you need to give some additional information about your class. You can easily do this with adding a note, which looks like this:
Keep in mind that these models are created with Visual Modeler, which is delivered with Visual Studio Enterprise Edition, so you can try and draw them yourself. I’m not going to explain how Visual Modeler works here, look in the manual or MSDN Library for more information.
Future versions of this document
I think this document is a good step in OOP and UML. Together with the document " Different styles of Programming " they provide a good first-step tutorial in the world of Object Oriented Programming.
There are many features of UML which are not covered in this document. One of them are so-called "use-cases" which is a story on its own. This will be explained in a new document which has yet to be written at this moment.