Learning Objectives
- Explain the basic steps involved in an object-oriented design process
- Explain the difference between is-a, has-a and uses-a relationships and explain how to implement each type
- Perform an object-oriented design given a set of test requirements and produce as a result and appropriate UML class diagram
- Implement a UML class diagram
The typical object-oriented design process can be divided into four main stages:
Each of these stages is described in the following sections.
A key stage in object-oriented design is to identify objects given a program specification, or a description of the problem domain. The programmer should work out what things the program will need to deal with and note down potential classes. As well as identifying whether certain objects will allow the program to accomplish some of its objectives.
Once a candidate list of potential objects has been identified, the relationships that link these objects can be thought about. There are three main types of relationship:
To illustrate a use a relationship, consider an a theoretical program which represents information about simultaneous linear equations of the form:
In this problem domain, all the equations have an order (an integer). It should be possible to display all equations. The linear equations should have three floating point coefficients (e.g. 2, 1 and 7 for the first equation shown above). Simultaneous equations should consist of 2 linear equations. It should be possible to display such a set of simultaneous linear equations in the form shown above, and also to solve the equations, i.e. display the values of x and y which satisfy the equations. A UML diagram which satisfies the conditions of this problem would look like so:
UML DIAGRAM
In this UML diagram a simultaneous equation has a linear equation (in fact it has two). To implement this has a relationship, composition is used. However simultaneous equations can also use a linear equation, if we want allow the program to possibly calculate more than just the scenario given.
Aggregation is used to implement a uses a relationship. Aggregation is similar to composition, but rather than making one class a data member of the other, we make a pointer to an instance of the contained class a data member of the contained class. The pointer typically points to an instance of the contained class that already exists outside of the the scope of the containing class. Therefore, if the containing class is destroyed, the contained class can continue to exist.
To show the distinction between composition and aggregation consider the following two alternative implementations of the simultaneous equations problem domain.
class SimultaneousEquations
{
public:
SimultaneousEquations(){}
void SetEquations(LinearEquation e1,
LinearEquation e2)
{ _e1 = e1; _e2 = e2; }
void Display()
{ _e1.Display(); _e2.Display(); }
void Solve();
protected:
LinearEquation _e1, _e2;
}
class SimultaneousEquations
{
public:
SimultaneousEquations(){}
void SetEquations(LinearEquation *e1,
LinearEquation *e2)
{ _e1 = e1; _e2 = e2; }
void Display()
{ _e1->Display(); _e2->Display(); }
void Solve();
protected:
LinearEquation *_e1, *_e2;
}
The main difference between these two implementations is when using composition the type of data members is LinearEquation
whereas when using aggregation it is LinearEquation*
. This difference means that a SimultaneousEquations
instance does not have a LinearEquation
, it uses one that must already exist outside of the SimultaneousEquations
class. In other words, the existence of the LinearEquation
instances does not depend upon any SimultaneousEquations
instance of which they are part.
For example, consider the following excerpt from the main()
function that makes use of these classes when implemented using aggregation:
SimultaneousEquations s;
LinearEquation 11, 12;
l1.SetCoeffs(2.0, 1.0, 7.0);
l2.SetCoeffs(3.0, -1.0, 8.0);
s.setEquations(&l1, &l2);
s.Display();
s.Solve();
SimultaneousEquations s2;
LinearEquation 13;
l3.SetCoeffs(2.0, 4.0, -5.0);
s2.setEquations(&l1, &l3);
s2.Display();
s2.Solve();
Note that the &
operator is used when passing the LinearEquation
instances into the SimultaneousEquations
instances. This is because the SetEquations
member function expects pointer arguments
The l1
instance of LinearEquation
is used in both the s
and s2
instances of SimultaneousEquations
. This means that any change to l1
will result in a change to both s
and s2
. If composition was used instead, then copies of l1
would have been made and passed into s
and s2
instances, so any subsequent changes to l1
would not affect s
and s2
.
UML CLASS DIAGRAM
Notice in the above UML class diagram, aggregation is represented with an unfilled diamond, compared to the earlier composition which is represented by a filled diamond
The interactions between objects are defined by their public interfaces, which is their corresponding set of public attributes and behaviours. The public interface of an object should be carefully considered, and whether it should be available to the entire program, restricted to the class hierarchy or restricted to the individual class itself. To illustrates the process of identifying attributes and behaviours, let’s return to the simultaneous equations example again.
The attributes are fairly straightforward, and were mentioned when the problem was set up. By logical thinking about the problem, the following behaviours can be identified:
Solve()
a public member function of SimultaneousEquations
GetA()
GetB()
GetC()
a public inspector function in LinearEquation
that provide access to the coefficients of the equation.Display()
a public type dependent member function in the Equation
inheritance hierarchyDisplay()
a public member function in the SimultaneousEquations
classNote that candidate objects can be eliminate whilst identifying attributes/behaviours
The driver can be thought of as the glue that binds the objects together, or the main algorithm of the program that makes use of the objects. In C++ the driver algorithm of the program corresponds to the main()
function.
BASIC UML DIAGRAM
The basic format of class boxes in UML is illustrated below. Remember <<abstract>>
above the class
names indicates that the class is an abstract base class. All other classes will be concrete classes. The symbol to the left of each data member or member function indicates its visibility, i.e.
+
public#
protected-
privateUML DIAGRAM
The scope of attributes and behaviours can also be indicated using UML. static
data members are common to all instances of the class (i.e. the have class scope), whereas non-static members are specific to an instance of a class (i.e. they have instance scope). In UML terminology, C++ static
members are known as classifier members, and non-static members are known as instance members. The UML notation for classier members is to underline as illustrated below.
UML DIAGRAM
UML notation for generalisation relationships is an arrow on the line from the derived class to the base class. The notation for has a (i.e. composition) relationships is a filled diamond on the lien from the containing class to the contained class, with the diamond being at the containing class end. The notation for uses a relationship (i.e. aggregation) is an unfilled diamond instead of a filled diamond. Finally for a virtual function (a type dependent operation in an inheritance hierarchy) the UML notation is italics.