02291: System Integration Design By Contract and OCL Hubert - - PowerPoint PPT Presentation
02291: System Integration Design By Contract and OCL Hubert - - PowerPoint PPT Presentation
02291: System Integration Design By Contract and OCL Hubert Baumeister huba@dtu.dk DTU Compute Technical University of Denmark Spring 2019 What does this function do? Implementation public List<Integer> f(List<Integer> vector) {
What does this function do?
Implementation
public List<Integer> f(List<Integer> vector) { if (vector.size() <= 1) return vector; int k = vector.elementAt(0); List<Integer> less = new List<Integer>(); List<Integer> equal = new List<Integer>(); List<Integer> bigger = new List<Integer>(); g(k,vector,less,equal,bigger); List<Integer> r = f(less); r.addAll(equal); r.addAll(f(bigger)); return r; } public void g(int k, List<Integer> vector, List<Integer> less, List<Integer> equal, List<Integer> bigger) { for (int i : vector) { if (i < k) less.add(i); if (i == k) equal.add(i); if (i > k) bigger.add(i); } }
Contract for the sort function in OCL
Specification (Contract) in OCL
context C::sort(a : Sequence(Integer)) : Sequence(Integer) pre: a <> null post: isSorted(result) and sameElements(a,result) def: isSorted(result) = Sequence {1..a.length}->forAll(i | Sequence {1..a.length}->forAll( j | i < j implies a->at(i) <= a->at(j)) def: sameElements(a1, a2 : Sequence(Integer)) = a1->forAll( i | a1->count(i) = a2->count(i)) and a2->forAll( i | a1->count(i) = a2->count(i))
Contracts
Counter i : int inc() : void dec() : void {The field i should always be greater than 0.} {The inc operation increases i by one.} {The operation dec should be called only if i is greater than
- zero. In this case i is decremented
by one.}
public T n(T1 a1, .., Tn an, Counter c) ... c.dec(); ...
Contract for dec
◮ Method n ensures pre-condition c.i > 0 before calling
c.dec()
◮ Method dec ensures counter is decremented, only when
c.i > 0
→ Undefined what happens when c.i <= 0
Example
Counter i : int inc() : void dec() : void {The field i should always be greater than 0.} {The inc operation increases i by one.} {The operation dec should be called only if i is greater than
- zero. In this case i is decremented
by one.}
Example
Counter i : int inc() : void dec() : void {The field i should always be greater than 0.} {The inc operation increases i by one.} {The operation dec should be called only if i is greater than
- zero. In this case i is decremented
by one.}
Counter inc() : void dec() : void i : int {context Counter inv: i >= 0} {context Counter :: inc ( ) post: i = i@pre + 1} {context Counter :: dec ( ) pre: i > 0 post: i = i@pre - 1 }
Object Constraint Language (OCL)
Express constraints in UML diagrams in a formal way 1 Invariants of UML diagrams 2 The contract of operations (i.e. pre- and postconditions
- f operations)
3 body of query operations ({query}; don’t change the state)
query operations can be used in OCL constraints, but not state changing operations
Bank example with constraints
Bank Account
update(n : int) : void bal : int
History
History() : void bal : int 0..1 prev 1 1 0..1 1
- wner
0..* accounts {context Bank inv: accounts->forAll(a | a.owner = self) {inv: bal >= 0} {pre: bal + n >= 0 post: bal = bal@pre + n and history.oclIsNew() and history.bal = bal@pre and history.prev = history@pre}
Update operation of Account
State before executing update(n)
a: Account bal = b h: History bal = m { n + b > = 0 }
Update operation of Account
State before executing update(n)
a: Account bal = b h: History bal = m { n + b > = 0 }
State after executing update(n)
h1: History bal = b h: History bal = m a: Account bal = b + n prev
OCL Syntax
◮ More on OCL operators and OCL
◮ Online on Safari books: ”Object Constraint Language” by
Jos Warmer, Anneke Kleppe (cf. course home page)
OCL expressions and their evaluation
◮ OCL expression evaluated in a context.
◮ Class, Operation, Association, ...
◮ Expression cannot change the state of the system
→ can’t call state changing operations (basically no method calls are allowed) → Exception: calls to methods marked {query} are allowed → No assignment operator (= means equality)
Type of constraints
The type of the constraint
◮ Most common
◮ Invariant for classes (inv:) ◮ precondition for operations (pre:) ◮ postcondition for operations (post:)
◮ Others
◮ body:
exp
◮ def:
name(parameter:type) : type = expr
- Ex. def:
initial : String = name.substring(1,1)
- Ex. def:
getTotalPoints(d:Date) : Integer = OCL expression
Notation
◮ Within notes in {}
{pre: i > 0 post: i = i@pre -1} {inv: i >= 0} {post: i = i@pre + 1} Counter i : int inc dec
◮ As separate text.
◮ In this case the context part must be present
context Counter inv: i >= 0 context Counter :: inc post: i = i@pre + 1
OCL Syntax (simplified): Available types
◮ Basic datatypes: Integer, Real, Boolean, String ◮ Boolean operators: =, <>, not, and, or, implies, . . . ,
x.isOCLTypeOf(C)
◮ All classes C from the UML model ◮ Collection types: Set(C), OrderedSet(C), Bag(C),
Sequence(C)
OCL Syntax (simplified): Basic operations
b a A y:int m() B x:int ◮ Attribute / Navigation (in the context of class A)
◮ self ◮ self.b.x
◮ Operation Calls
◮ self.m(c1, · · · , cn) (Method call) ◮ self.b → cop(. . .) (Call to a method on collections)
context A inv: self.b.x->includes(3)
Navigation
Navigation via dot (.) notation to roles and attributes
b a A y:int B x:int
b1:B { x = 5 } a1:A { y = 1 0 }
a1.b = a1.b.x =
Navigation
Navigation via dot (.) notation to roles and attributes
b a A y:int B x:int
b2:B {x = 5} a1:A {y = 10} b1:B {x = 3}
a1.b = a1.b.x =
OCL Syntax (simplified): OCL Boolean Operators and Collections
◮ Boolean operators
◮ Equal (=), Not-Equal (<>) ◮ not, and, or, implies, . . . ◮ x.isOCLTypeOf(C) is true if x is an object of type C or a
subtype thereof
◮ Collections
◮ Collection(C): Abstract supertype ◮ Set(C) (not ordered, no duplicates) ◮ OrderedSet(C) (ordered, no duplicates) ◮ Bag(C) (not ordered, allowes duplicates) ◮ Sequence(C) (ordered, allowes duplicates)
OCL Syntax (simplified): Operations on collections I
◮ collection→forAll(x | P[x]) corresponds to
∀x ∈ collection : P[x]
◮ collection→exists(x | P[x]) corresponds to
∃x ∈ collection : P[x]
◮ collection→select(x | P[x]) corresponds to
{x ∈ collection | P[x]}
◮ collection→collect(x | E[x]) corresponds to
{E[x] | x ∈ collection}
◮ self.employee→collect(birthday) =
self.employee.birthday
P[x] and E[x] means that x occures in predicate P and expression E
OCL Syntax (simplified): Operations on collections II
◮ Sequence{1, 2, 3}→includes(3) = true
◮ more: includesAll(collection), excludes, isEmpty, union,
intersection, count . . .
◮ compare with
◮ Sequence{1, 2, 3}→including(5) = Sequence {1, 2, 3, 5 }
◮ Conversions between collections: asSet, asBag,
asSequence, . . .
◮ C.allInstances() ◮ sequence→at(i) (s→at(1) is the first element)
Flattening Collections
Person Club * member * Federation * 1
f.club.member = f.club->collect(member) = f.club->collectNested(member) =
OCL Syntax (simplified): Postconditions
◮ Additionally in postconditions:
◮ result ◮ x@pre, x.bal@pre ◮ x.oclIsNew() ◮ xˆm() ◮ ”Message m was send to x” ◮ e.g. observerˆupdate
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed context Observable::clearChanged() post: not(changed)
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed context Observable::clearChanged() post: not(changed) context Observable::hasChanged() post: result = changed
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed context Observable::clearChanged() post: not(changed) context Observable::hasChanged() post: result = changed context Observable::hasChanged() body: changed
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed context Observable::clearChanged() post: not(changed) context Observable::hasChanged() post: result = changed context Observable::hasChanged() body: changed context Observable::addObservers(Observer o) post: observers = observers@pre->including(o)
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed context Observable::clearChanged() post: not(changed) context Observable::hasChanged() post: result = changed context Observable::hasChanged() body: changed context Observable::addObservers(Observer o) post: observers = observers@pre->including(o) context Observable::notifyObservers(Object arg) pre: changed post: observers->forAll( o | oˆupdate(self, arg)) and not(changed)
Observer Pattern
Observable changed : bool addObserver(Observer o) hasChanged() : bool setChanged() clearChanged() notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::setChanged() post: changed context Observable::clearChanged() post: not(changed) context Observable::hasChanged() post: result = changed context Observable::hasChanged() body: changed context Observable::addObservers(Observer o) post: observers = observers@pre->including(o) context Observable::notifyObservers(Object arg) pre: changed post: observers->forAll( o | oˆupdate(self, arg)) and not(changed) context Observable::notifyObservers(Object arg) post: changed@pre implies (observers->forAll( o | oˆupdate(self, arg)) and not(changed))
Observer Pattern Final
Observable changed : bool addObserver(Observer o) notifyObservers(Object arg) «Interface» Observer update(Observable o, Object arg) *
context Observable::addObservers(Observer o) post: observers = observers@pre->including(o) context Observable::notifyObservers(Object arg) post: changed@pre implies (observers->forAll( o | oˆupdate(self, arg)) and not(changed))
Example Vending Machine
Vending machine behaviour with OCL constraints
{def: itemDispensed(f:Fruit, oldCm:int) = dispensedItem = f and restMoney = oldCm - priceFor(f) and currentMoney = 0 and totalMoney = totalMoney + priceFor(f) } {inv: currentMoney >= 0 and totalMoney >= 0 and restMoney >= 0 } {pre: m > 0 post: if (selectedFruit <> null and enoughMoney(selectedFruit)) then itemDispensed(f,currentMoney@pre + m) else currentMoney = currentMoney@pre + m endif } {post: if enoughMoney(f) then itemDispensed(f,currentMoney@pre) else selectedFruit = f endif } {pre: (f = banana) or (f = apple) body: if (f = banana) then 5 else 3 } {post: currentMoney = 0 and restMoney = currentMoney@pre and selectedFruit->size() = 0 } {body: currentMoney >= priceFor(f)} {body: fruit->collect(f1 | f1 = f)->size() >= 1} {pre: enoughMoney(f) and hasFruit(f) post: itemDispensed(f,currentMoney@pre) } 0..1 selectedFruit 0..1 dispensedItem * fruit «enumeration» Fruit banana apple VendingMachine currentMoney: int totalMoney: int restMoney: int input(m:int) select(f:Fruit) cancel() enoughMoney(f:Fruit) {isQuery} hasFruit(f:Fruit) {isQuery} dispense(f:Fruit) priceFor(f:Fruit) {isQuery}