Mapping UML Designs to Code

Behavioral Aspects

Step 8

State Machine Implementation

open parenthesis

State Machines

  • Represent the behavior of a class (or component) in terms of its reactions to changes form its environment.

  • Composed of States, connected by Transitions, triggered by Events

States and Transitions

uml states transitions

Transitions receive and send events, have guards and actions

uml events
close parenthesis

Implementation Approaches

  1. Enumerations/Integers.

  2. State design pattern.

State Machine example

Diagram
Figure 1. Book States

Enumeration Approach

Rationale:
  • Use an enumeration to represent states.

  • Verify and change states inside each concerned method.

Enumeration Approach (Cont.)

Diagram
Diagram

Enumeration implementation

Diagram
public enum State {
    oredered, available, borrowed, reserved
}

«return()» Operation Implementation

Diagram
Method return():
  • cannot be executed if state is ≠ Borrowed

  • sets new state to either Available or Reserved

public void returnBook() {
 if (state != State.borrowed)
  throw new IllegalStateException();

 // Manage return
 // Set State to Available or Reserved

 if(state != State.available &&
  state != State.reserved)
     throw new IllegalStateException();
}

Enumeration Approach Wrap-up

  • Easy to implement, fast

  • Hard to modify: new states or new transitions impact the whole code

State Pattern Approach

Apply the State design pattern:
  • Create a class for each State.

  • Delegate the behavior to the these classes

  • Throw an exception when an operation is called in the wrong state.

Create a class for each state

Diagram
Diagram

Delegate the behavior to the sate class

public class BookWithStates implements Book {

    private BookState state = new Ordered();

    @Override
    public void reserve(Reader aReader) {
        state.reserve(aReader);
    }

    @Override
    public void deliver() {
        state.deliver();
    }

    @Override
    public void borrow() {
        state.borrow();
    }

    @Override
    public void returnBook() {
        state.returnBook();
    }
}

Default behavior

public class DefaultBookState implements BookState {

    @Override
    public void reserve(Reader aReader) {
        throw new IllegalStateException();
    }

    @Override
    public void deliver() {
        throw new IllegalStateException();
    }

    @Override
    public void borrow() {
        throw new IllegalStateException();
    }

    @Override
    public void returnBook() {
        throw new IllegalStateException();
    }
}

The Ordered State

class Ordered extends DefaultBookState {

    @Override
    public void deliver() {
        // Manager deliver
        // Change state to Available or Reserved
    }

    @Override
    public void reserve(Reader aReader) {
        // Manage reservation
        // State remains Ordered
    }
}

State pattern Approach Wrap-up

  • Harder to implement: class proliferation (1 class per state).

  • Easier to modify: new states or new transitions have a limited impact.

Step 9

Operation Implementation

UML Operation Specification

Alternatives:
  • Operation pre- and post-conditions

  • Activity diagrams

Operation pre- and post-conditions

  • Based on «procedural abstractions».

    • Technique to document/specify what the operation does, without indicating how it works.

  • Pre-condition: a statement or set of statements that outlines a condition that should be true, or conditions that should be true, when the operation is called.

    • The operation is not guaranteed to perform as it should unless the pre-conditions have been met.

Operation pre- and post-conditions (Cont.)

  • Post-condition: is a statement or statements describing the condition that will be true when the operation has completed its task.

    • If the operation is correct and the pre-condition(s) met, then the post-condition is guaranteed to be true.

Pre- and post-conditions in UML

  • In UML pre- and post-conditions are precisely specified in OCL

  • OCL states for "The Object Constraint Language"

  • It is a pure expression language:

    • An OCL expression is guaranteed to be without side effect.

    • It cannot change anything in the model.

Example: library operations

library interface
  • Operation Library::return(bookId:Integer)

  • post-conditions:

    • the book becomes available.

    • the member no longer has the book.

In OCL

context Library::return(bookId:Integer)
pre:
  bookId > 0
post:
  let borrowing = self.borrowings->
    select(each| each.book.id = bookId)->last() in

  -- the book becomes available
  borrowing.book.oclInState(Available) and
  -- the member no longer has the book
  borrowing.member.books->excludes(borrowing.book)

In OCL (Cont.)

library interface
  • Operation Library::borrow(bookId:Integer, memberId:Integer):Boolean

  • post-conditions:

    • the book is unavailable

    • the member has the book in his borrowed books.

    • the library keeps a trace of the borrowing.

In OCL (Cont.)

context Library::borrow(bookId:Integer, memberId:Integer):Boolean
post:
  let book = self.books[bookId] in
  let member = self.members[memberId] in

  -- the book is unavailable
  book.oclInState(Unavailable) and

  -- the member has the book in his borrowed books.
  member.borrowed->includes(book) and

  -- the library keeps a trace of the borrowing.
  self.borrowings->select(each | each.member = member and
      each.book = book)->exists(each | each.oclIsNew())

Implementing pre- and post-conditions

Rationale:
  • Raise exceptions when pre-conditions are not respected

  • Deduce implementation from post-conditions

Book return operation

context Library::return(bookId:Integer)
pre:
  bookId > 0
post:
  let borrowing = self.borrowings->
    select(each| each.book.id = bookId)->last() in

  -- the book becomes available
  borrowing.book.oclInState(Available) and
  -- the member no longer has the book
  borrowing.member.books->excludes(borrowing.book)
public void returnBook(Integer bookId) {
 Validate.isTrue(bookId > 0);

 Borrowing borrowing = borrowings.stream()
         .filter(each -> each.book().id() == bookId)
         .findFirst()
         .get();
 borrowing.book().returnBook();
 borrowing.member().borrowed().remove(borrowing.book());
}

Pre- and post-conditions Wrap-up

  • Precise and easy to specify (if you know OCL), for simple operations.

  • Not adapted for more complex operations: concurrency, distribution, etc.

Activities diagram

  • Activity diagrams specify the behavior of a UML Operation.

  • They are composed of a set of Activities and (data and/or control) Flows.

open parenthesis

UML Activity Diagram

uml activities

Parameters, pre- and post-conditions

uml activities parameter

Data and control flows

uml activities flow
close parenthesis

Book borrow

borrow book

Borrow implementation

borrow book
public boolean borrow(Integer memberId, Integer bookId) {
 Member member;
 if (!books.containsKey(bookId)) {return false;}

 Book book = books.get(bookId);
 if (!book.isAvailable()) { return false;}
 if (!members.containsKey(memberId)) {
     member = this.registerMember(memberId);
 } else {
     member = members.get(memberId);
     if (member.isMaxQuotaExceed()) {
         return false;
     }
 }
 Borrow borrow = new Borrow(member, book);
 this.borrows.add(borrow);
 return true;
}

Activities diagram Wrap-up

  • Useful to specify complex algorithms.

  • Can be very precise.

  • Hard to use.

Last Step

Use Sequence Diagrams to implement tests

open parenthesis

Sequence diagrams

  • Examples of cooperation between objects.

  • Illustrate the dynamic sequence of a process through messages exchanged between objects.

  • Time is represented as an explicit (vertical) dimension.

Sequence diagrams Example

uml sequence
close parenthesis

Book reservation

sd reservation

Map Sequence diagrams to test cases

sd reservation
class LibraryTest {

  private Library lib = new Library();

  @BeforeEach
  void setUp() {
        lib.createMember(42, "Philippe");
        lib.createBook(1, "Baudolino");
  }

  @Test
  void reserve() {
    Calendar date = Calendar.getInstance();
    date.set(2017, 2, 1);
    lib.reserve("Baudolino", 42, date);

    Assertions.assertTrue(lib.reservations()
      .stream()
      .anyMatch(each -> each.member().id() == 42 &&
        each.book().title().equals("Baudolino") &&
        each.date().equals(date))
    );
  }
}

Sequence diagrams Wrap-up

  • Sequence diagrams represent execution traces.

  • They illustrate the collaboration among objects.

  • They are not adapted to specify algorithms (conditionals, loops, etc.).

Conclusion

  • UML (and its different diagrams) can be useful to specify design details and clarify choices.

  • Each diagram has a different proposal, but they all represent the same model.

  • Designers and developers must have a clear and common idea about how to map designs to code.