272: Software Engineering Fall 2008: Instructor: Tevfik Bultan Lecture 4: Object Constraint Language
272: Software Engineering Fall 2008: Instructor: Tevfik Bultan Lecture 4: Object Constraint Language
272: Software Engineering Fall 2008: Instructor: Tevfik Bultan Lecture 4: Object Constraint Language
UML
UML can be used in all phases of software development specification of requirements, architectural design, detailed design and implementation There are different types of UML diagrams for specifying different aspects of software: Functionality, requirements Use-case diagrams Architecture, modularization, decomposition Class diagrams (class structure) Component diagrams, Package diagrams, Deployment diagrams (architecture) Behavior State diagrams, Activity diagrams Communication, interaction Sequence diagrams, Collaboration diagrams
1..*
Product
remind() billForMonth(Int) {creditRating()=poor} 1..* indicates that credit Sales rating is always Rep 0..1 set to poor for a Personal Customer Employee
Sequence Diagrams
A sequence diagram shows a particular sequence of messages exchanged between a number of objects Sequence diagrams also show behavior by showing the ordering of message exchange A sequence diagram shows some particular communication sequences in some run of the system it is not characterizing all possible runs
[check=true] <<create>>
:DeliveryItem
object message
Sequence numbers are used to show the time ordering among the messages
sequence number
link
:Order
1:prepare()
1.1:*prepare()
:ProductOrder
1.1.2.1:needsToReorder()
:StockItem
1.1.1:check() 1.1.2:[check==true]remove()
1.1.2.2:new
:DeliveryItem
1.1.3:[check==true]new
:ReorderItem
State Diagrams
State diagrams are used to show possible states a single object can get into shows states of an object How object changes state in response to events shows transitions between states Uses the same basic ideas from statecharts and adds some extra concepts such as internal transitions, deferred events etc. A Visual Formalism for Complex Systems, David Harel, Science of Computer Programming, 1987 Statecharts are basically hierarchical state machines Statecharts have formal semantics
State Diagrams
Hierarchical grouping of states composite states are formed by grouping other states A composite state has a set of sub-states Concurrent composite states can be used to express concurrency When the system is in a concurrent composite state, it is in all of its substates at the same time When the system is in a normal (non-concurrrent) composite state, it is in only one of its substates If a state has no substates it is an atomic state Synchronization and communication between different parts of the system is achieved using events
cancelled
Cancelled
Delivered
Checking
Dispatching
Delivered
Authorizing
Authorized this transition can only be taken after both concurrent states reach their final states
Rejected
Activity Diagrams
Activity diagrams show the flow among activities and actions associated with a given object using: activity and actions transitions branches merges forks joins Activity diagrams are similar to SDL state diagrams, SDL state diagrams have formal semantics Activity diagrams are basically an advanced version of flowcharts
Finance Receive Order *for each order item Authorize Payment [failed] Cancel Order Reorder item Check Order Item [in stock] Assign to Order
Stock Manager
Choose Outstanding Order Items * for each chosen order item Assign to Order
[succeeded]
vertical lines are used to separate swimlanes to show which activities are handled by which part of the system
[need to reorder]
[stock assigned to all order items and payment authorized] Dispatch Order
UML Diagrams
Functionality, requirements use case diagrams Architecture, modularization, decomposition class diagrams (class structure) component diagrams, package diagrams, deployment diagrams (architecture) Behavior state diagrams, activity diagrams Communication, interaction sequence diagrams, collaboration diagrams
OCL specification is available here: http://www.omg.org/technology/documents/formal/ocl.htm My slides are based on the following text: The Objection Constraint Language: Precise Modeling with UML, by Jos Warmer and Anneke Kleppe
Advantages of Constraints
Better documentation Constraints add information about the model elements and their relationships to the visual models used in UML It is a way of documenting the model More precision OCL constraints have formal semantics, hence, can be used to reduce the ambiguity in the UML models Communication without misunderstanding UML models are used to communicate between developers Using OCL constraints modelers can communicate unambiguously
OCL Constraints
OCL constraints are declarative They specify what must be true not what must be done OCL constraints have no side effects Evaluating an OCL expression does not change the state of the system OCL constraints have formal syntax and semantics their interpretation is unambiguous
An Example
Loyalty programs are used by companies to offer their customers bonuses (for example frequent flier miles programs) There may be more than one company participating in a loyalty program (each participating company is called a program partner) A customer who enters a loyalty program gets a membership card. Program partners provide services to customers in their loyalty programs. A loyalty program account can be used to save the points accumulated by a customer. Each transaction on a loyalty program account either earns or burns some points. Loyalty programs can have multiple service levels
An Example
partners ProgramPartner 1..* 1..* LoyaltyProgram program 0..* enroll(c:Customer) 0..* 0..* actualLevel {ordered} 1..* ServiceLevel deliveredServices 0..* name: String Service 0..* condition: Boolean pointsEarned: Integer availableServices pointsBurned: Integer description: String Membership 0..1 Customer name: String title:String isMale: Boolean dateOfBirth: Date age(): Integer owner 0..* card
numberOfCustomers: Integer
transactions 0..* 0..* Transaction transactions points: Integer date:Date program(): LoyaltyProgram
CustomerCard card valid: Boolean validForm: Date goodThru: Date color: enum{silver, gold} printedName: String 0..* card transactions
Burning
Earning
Model Types
Model types are classes, subclasses, association classes, interfaces, etc. defined in the model Properties of a model type are attributes operations and methods navigations that are derived from the associations enumerations defined as attribute types Properties of a model type can be referenced in OCL expressions
Invariants
Using OCL we can specify class invariants such as
Customer age >= 18
The class on which the invariant must hold is the invariant context Invariant has to hold for all instances of the class For the above example, the expression age >= 18 is an invariant of the Customer class, i.e. it holds for every instance of that class
Invariants
We can also write invariants on attributes of associated classes In OCL you can use the rolename to refer to the object on the other end of an association. If the rolename is not present, you can use the classname starting with a lowercase letter Examples:
Membership card.owner = customer CustomerCard printedName = owner.title.concat( owner.name )
Choosing a Context
The class on which the invariant must be put is the invariant context One can write the same invariant property in different contexts For example
Customer age >= 18 LoyaltyProgram customer.forAll( age >= 18 )
Navigating Associations
Navigation starting from CustomerCard
CustomerCard owner.program ...
LoyaltyProgram 0..* 0..* Customer
program 0..*
owner card
CustomerCard
The owner attribute of the CustomerCard instance (which is an instance of the Customer class)
The program attribute of the owner attribute (which will be instances of the LoyaltyProgram class)
program 0..*
owner card
CustomerCard
Default multiplicity in UML is 1 Hence, each CustomerCard instance has exactly one owner and navigating from CustomerCard class to Customer class through the owner attribute results in a single instance of Customer class
CustomerCard printedName = owner.title.concat( owner.name ) A single instance of Customer
program 0..*
owner card
CustomerCard
If the multiplicity is greater than 1, navigation results in a collection of values Navigating from Customer class to LoyaltyProgram class through the program attribute results in a set of instances of the LoyaltyProgram class
Customer program->size <= 10 A set of instances of the LoyaltyProgram class Equivalently, this constraint can be specified as a multiplicity constraint by changing the multiplicity of the association between LoyaltyProgram and Customer from 0..* to 0..10 on the LoyaltyProgram side
program
Membership
Customer
CustomerCard
According to UML semantics an instance of an association class is associated with one instance of the classes in each side of the association Hence, each instance of Membership is associated with one instance of Customer and one instance of LoyaltyProgram If we navigate from Membership class to Customer we would get a single instance of Customer class
Membership card.owner = customer A single instance of Customer
program
Membership
Customer
CustomerCard
However, one class can be associated with multiple instances of an association class Hence, navigating from Customer to Membership results in a set of instances of the Membership class.
Customer membership.program = program
Qualified Associations
To navigate qualified associations you need to index the qualified association using a qualifier If there are multiple qualifiers their values are separated using commas Example
LoyaltyProgram enroll(c:Customer)
object.navigation[qualifierValue]
Collections
Often the multiplicity of an association is greater than 1, linking one object to a set of objects of the associated class In such a scenario navigating through the association results in a collection of objects from the association class OCL has a number of collection operations to write expressions in such situations Whenever the link in a constraint results in a set of objects, you can use one of the collection operations by putting an arrow between the rolename and the operation
Collections
The size of a collection can be expressed as:
LoyaltyProgram serviceLevel->size = 2
You can also select a subset of a list by passing an OCL expression as an argument to the select operation:
Flattening of Collections
In OCL collection types are automatically flattened Whenever a collection is inserted into another collection, the resulting collection is automatically flattened; the elements of the inserted collection become elements of the resulting collection for example, there is no set of sets of integers it is always just a set of integers or a bag of integers
Example: in OCL we do not have: Set{ Set{ 1, 2, 3 } Set{ 3, 4} } instead we get: Bag{ 1, 2, 3, 3, 4 } Flattening of a set of sets results in a Bag.
Collections
You can also use forAll operation to evaluate a condition on a collection forAll operation takes an OCL expression as an argument, and returns a boolean value If the argument of forAll operation evaluates to true for all members of the collection, then the result of the forAll operation is true, otherwise it is false Example
LoyaltyProgram partners.deliveredServices->forAll( pointsEarned = 0 and pointsBurned = 0 ) implies membership.loyaltyAccount->isEmpty
If there is no way to earn or burn points then there should be no loyalty accounts
This expressions is not correct since a customer can participate in more than one program In OCL, the rule is: If you navigate through more than one associations with multiplicity greater than one, you get bags. If you navigate through only one association with multiplicity greater than one you get a set. The correct constraint is:
including(object) adds one element to the collection (for a set the element is added if it is not in the set) excluding(object) removes one element from the collection (from a bag or sequence it removes all occurrences) size number of elements in the collection count(object) number of occurrences of the object in the collection includes(object) True if the object is an element of the collection includesAll(collection) True if all elements of the parameter collection are members of the collection isEmpty notEmpty
Select Operation
The result of the select operation is the collection that contains all elements for which the boolean expression (that is the parameter of the select operation) evaluates to true The result of the select operation is a subset of the original collection
Reject Operation
Reject operation is used to remove elements from a collection Returns the collection that contains all elements for which the boolean expression (that is the parameter of the select operation) evaluates to false Following two expressions are equivalent
CustomerCard self.transactions->select( points > 100 ) CustomerCard self.transactions->reject( not (points > 100) )
Collect Operation
Collect operation operates over a collection, computes an expression for each member of the collection and gathers the results in a new collection
LoyaltyAccount transactions->collect(points)
The result of a collection operation on a Set or a Bag is a Bag and on a Sequence is a Sequence General syntax is
One can specify the pre and postcondition of an operation of a class using OCL expressions
Type1::operation(arg: Type2) : ReturnType pre: arg.attr = true post: result = arg.attr xor self.attribute2
For example
You can refer to the return value of the method using the result keyword
An Example
partners ProgramPartner 1..* 1..* LoyaltyProgram program 0..* enroll(c:Customer) 0..* 0..* actualLevel {ordered} 1..* ServiceLevel deliveredServices 0..* name: String Service 0..* condition: Boolean pointsEarned: Integer availableServices pointsBurned: Integer description: String Membership 0..1 Customer name: String title:String isMale: Boolean dateOfBirth: Date age(): Integer owner 0..* card
numberOfCustomers: Integer
transactions 0..* 0..* Transaction transactions points: Integer date:Date program(): LoyaltyProgram
CustomerCard card valid: Boolean validForm: Date goodThru: Date color: enum{silver, gold} printedName: String 0..* card transactions
Burning
Earning
Iterate Operation
The select, reject, collect, forAll and exists operations can all be described as a special case of iterate operation The syntax is collection->iterate( element : Type1; result : Type2 = <expression> | <expression-with-element-and-result>) The element is the iterator, the resulting value is accumulated in the variable result (called accumulator). The accumulator is initialized to the <expression> The result of the iterate operation is a value accumulated by iterating over all elements in a collection.
Iterate Operation
Iterate operation corresponds to the following pseudo code
result = <expression>; while ( collection.notEmpty() do element = collection.nextElement(); result = <expression-with-element-and-result>; endwhile return result;
Enumerations
Values of an enumerated variable can be written as #valuename For example
Membership actualLevel.name = Silver implies card.color = #silver and actualLevel.name = Gold implies card.color = #gold
An Example
partners ProgramPartner 1..* 1..* LoyaltyProgram program 0..* enroll(c:Customer) 0..* 0..* actualLevel {ordered} 1..* ServiceLevel deliveredServices 0..* name: String Service 0..* condition: Boolean pointsEarned: Integer availableServices pointsBurned: Integer description: String Membership 0..1 Customer name: String title:String isMale: Boolean dateOfBirth: Date age(): Integer owner 0..* card
numberOfCustomers: Integer
transactions 0..* 0..* Transaction transactions points: Integer date:Date program(): LoyaltyProgram
CustomerCard card valid: Boolean validForm: Date goodThru: Date color: enum{silver, gold} printedName: String 0..* card transactions
Burning
Earning
Customer name: String title:String isMale: Boolean dateOfBirth: Date age(): Integer isRelatedTo(p: Customer): Boolean
However, this constraint is incorrect since it does not distinguish burning and earning transactions. To determine the subclass of an element that belongs to a collection, we can use the operation oclType and fix the above constraint as follows:
LoyaltyProgram partners.deliveredServices.transactions ->select(oclType = Burning) ->collect( points )->sum < 10,000
Example
Given the class structure shown in the diagram, following invariants for the Transaction class will hold
Transaction
Burning
Earning
Transaction self.oclType = Transaction self.oclIsKindOf(Transaction) = true self.oclIsTypeOf(Transaction) = true self.oclIsTypeOf(Burning) = false self.oclIsTypeOf(Earning) = false self.oclIsKindOf(Burning) = false self.oclIsKindOf(Earning) = false
Example
Given the class structure shown in the diagram, following invariants for the Burning class will hold
Transaction
Burning
Earning
Burning self.oclType = Burning self.oclIsKindOf(Transaction) = true self.oclIsTypeOf(Transaction) = false self.oclIsTypeOf(Burning) = true self.oclIsKindOf(Burning) = true self.oclIsTypeOf(Earning) = false self.oclIsKindOf(Earning) = false
Example
Given the class structure shown in the diagram, following invariants for the Earning class will hold
Transaction
Burning
Earning
Earning self.oclType = Earning self.oclIsKindOf(Transaction) = true self.oclIsTypeOf(Transaction) = false self.oclIsTypeOf(Burning) = false self.oclIsKindOf(Burning) = false self.oclIsTypeOf(Earning) = true self.oclIsKindOf(Earning) = true
class invariant
{ points >= 0 }
earn(i: integer) [i > 0] earn(i: integer) [i = 0] Empty do/checkItem NotEmpty do/initiate Delivery burn(i: integer) [points - i = 0] earn(i: integer) burn(i: integer) [points - i > 0]
Class Diagram of the Automatic Train Control System of Bay Area Rapid Transit System
Object Constraints
if for a segment there is a next segment, the begin of the next is equal to the end of the former Segment
self.next.isDefined implies self.next.segBegin = self.segEnd
Object Constraints
The length of a segment has to be the difference of segment end and begin Segment self.segEnd self.segBegin = self.length Connected segments belong to the same track Segment
self.next.isDefined implies self.track = self.next.track
Object Constraints
The origin and the destination of a train have to be connected by a sequence of segments
Train self.orig.nextPlus()->includes(self.dest)
Object Constraints
The segments that bound the region a station computer is responsible for are connected
StationComputer self.sb.nextPlus()->includes(self.se)
Object Constraints
A train should stay below the maximum speed that segment of track can handle", i.e. the civil speed. StationComputer
self.trains()->forAll(t | t.v <=t.currentSeg().civilSpeed) (note: v=velocity in class Train)
Object Constraints
"A train should not enter a closed gate". I.e. if a next closed gate exists, the distance to it is greater than the worst case stopping distance of the train. The train can stop in time. Remember that the position of a gate is at the end of the segment it bounds. StationComputer
self.trains()->forAll(t | t.nextClosedGate().isDefined implies t.nose+self.wcsd(t) < t.nextClosedGate().segment.segEnd) (note: wcsd() returns the worst case stopping distance of a train)
Object Constraints
"A train should never get so close to a train in front that if the train in front stopped suddenly (e.g., derailed) the (following) train would hit it". If a train in front exists, the distance to it is greater than the worst case stopping distance of the (following) train.
self.segEnd-self.segBegin = self.length
self.next.isDefined implies self.track = self.next.track
Train self.orig.nextPlus()->includes(self.dest)