The object constraint language was covered as part of the Software Engineering module (COM00144M) during my MSc studies with the University of York. This post will provide information on why OCL is used and examples of how you can implement OCL.
What is OCL?
The object constraint language (OCL) is a declarative ISO standard language created by IBM. OCL was developed to help relieve limitations with UML modelling and to allow the specification of more precise aspects of a system design. OCL is a part of the Unified Modelling Language (UML) set by the Object Management Group (OMG) as a standard for object-oriented design.
OCL is a declarative and side-effect free language which means the constraints only enforce rules, they do not assign values or modify the system.
Some usages of OCL include:
- Invariants specify constraints that have to equal true during the instance of an objects lifetime
- Preconditions specify constraints that have to equal true before a method is run
- Postconditions specify constraints that have to equal true after a method is run
- Initialisation specify default property values
This post includes various examples, all of these examples are relative to this conceptual class diagram.
Invariant
An invariant is a constraint that has to be true during the lifetime of an instantiated object. This works as a guarantee that the values of a model always satisfy the requirements.
Invariant Syntax
context <classifier>
inv [<constraint name>]: <Boolean OCL expression>
Invariant Examples
context Earth
-- earth has more surface water than surface land
inv: waterSurfaceArea > landSurfaceArea
-- earth should only have one moon
inv: satellite->selectByKind(Moon)->size() = 1
-- if population less 0, life must exist
inv: population > 0 implies planet.lifeExists() = true
-- the name of the earth must consist of 5 characters
inv: name.size() = 5
context Mars
-- mars day length is more than 24 hours
inv: getDayLength > 24
-- there is no life on mars (that we know of)
inv: lifeExists() = false
context Human
-- a human must have an age of at least 0
inv: age >= 0
-- all humans must live on a planet
inv: livesOn->notEmpty()
-- all humans must live on earth
inv: livesOn->forEach(p Planet: p.oclIsTypeOf(Earth))
Precondition
A precondition is a constraint that has to be true prior to the running of an operation or method. This works as a guarantee the values are as expected before an alterations take place and the values satisfy the model requirements.
Precondition Syntax
context <classifier>::<operation>> (parameters)
pre [<constraint name>]: <Boolean OCL expression>
Precondition Examples
context Earth::addHuman(h:Human)
-- the object being added must be a of Human type
pre: h.oclIsTypeOf(Human)
-- population must not already include this object
pre: population->excludes(h)
context Earth::launchSatellite(s:ManMadeSatellite)
-- the object being added must be a of ManMadeSatellite type
pre: s.oclIsTypeOf(ManMadeSatellite)
-- the earth satellite budget must be less than all satellite cost
pre: satelliteBudget >= satellite->selectByKind(ManMadSatellite).getCost()->sum() + s.getCost()
-- this object being added must not already exist as a satellite
pre: satellite->excludes(s)
context InnerPlanet::hitByAsteroid(a:Asteroid)
-- the object must be of type Asteroid
pre: a.oclIsTypeOf(Asteroid)
context Earth::hitByComet(c:Comet)
-- the object must be of type c:Comet
pre: c.oclIsTypeOf(Comet)
-- in this example, life exists prior to the Comet hitting
pre: lifeExists() = true
pre: population = population->size()
Postcondition
A postcondition is a constraint that has to be true after to the running of an operation or method. This works as a guarantee the values are as expected after alterations take place and the new values satisfy the model requirements.
Postcondition Syntax
context <classifier>::<operation>> (parameters)
post [<constraint name>]: <Boolean OCL expression>
Postcondition Examples
context Earth::addHuman(h:Human)
-- the earth population must include the added Human
post: population->includes(h)
context Earth::launchSatellite(s:ManMadeSatellite)
-- the earth satellites must include the added s:ManMadeSatellite
post: satellite->includes(s)
context InnerPlanet::hitByAsteroid(a:Asteroid)
-- the planets mass will increase by the mass of the Asteroid
post: mass = mass@pre + a.getMass()
-- the amount of craters will have increased
post: craterCount = craterCount@pre + 1
context Earth::hitByComet(c:Comet)
-- all life will cease to exist
post: lifeExists() = false
-- the population will be zero
post: population = 0
-- the size of the population collection will be zero
post: population->size() = 0
String Operations
A set of standard operations that can be used on strings. They are used in OCL expressions using a period symbol followed by the operation: attribute.size()
.
size(): Integer
Returns the number of characters in self
concat(s:string) : String
Returns the concatenation of self and s
substring(s:String) : String
Returns the sub-string of self
toUpperCase() : String
Returns string as uppercase
toLowerCase() : String
Returns string as lowercase
The full list of operations can be found on p.160 of the OCL Spec.
Collection Operations
A set of standard operations that can be used on collections. They are used in OCL expressions using a hyphen and arrow: collection->size()
.
size: Integer
Returns number of elements in the collection
includes(o:OclAny) : Boolean
Returns true if element o exists in the collection
excludes(o:OclAny) : Boolean
Returns true if element o does not exist in the collection
count(o:OclAny) : Integer
Returns how many times an element is contained in the collection
isEmpty: Boolean
Returns true if the collection is empty
notEmpty: Boolean
Returns true if the collection is not empty
including(o:OclAny)
Collection containing all elements of the Collection plus element o
excluding(o:OclAny)
Collection containing all elements of the Collection excluding(o:OclAny)
select(expr:oclExpression)
Subset of all elements of the collection, for which the OCL-expression expr is true
sum: Integer | Real | UnlimitedNatural
Returns addition of all elements within self
exists(v: Type | boolean-expression-with-v)
exists(v | boolean-expression-with-v)
exists(boolean-expression)
Specify a Boolean expression that must hold true for at least one object in a collection
forAll(v: Type | boolean-expression-with-v)
forAll(v | boolean-expression-with-v)
forAll(boolean-expression)
Specify a Boolean expression that must hold true for all objects in a collection
The full list of operations can be found on p.28 of the OCL Spec.
Type Operations
A set of standard operations that can be used in relation to the type of an object.
oclIsTypeOf(t:Classifier): Boolean
An operator used to check if self and t are of the same type but not a subtype
oclIsKindOf(t:Classifier): Boolean
An operator used to check if self and t are of the same type or a subtype
The full list of operations can be found on p.153 of the OCL Spec.