Mapping UML Designs to Code

Structural Aspects

Plan

  • Introduction

  • Implementation Strategy

  • Classes

  • Attributes

  • Associations

Mapping Designs to Code

Translating a UML model to object-oriented source code is a difficult task:
  • UML has several diagrams representing different aspects of the model.

  • The mapping from UML concepts (classes, associations, signals, states, operations, etc.) to OO concepts (classes, fields, and methods) is not trivial.

  • UML lacks semantics: there is no universal rule for mapping design to code.

The developer needs an «Implementation Strategy» to guide the translation.

Implementation Strategy

  • A set of rules that specifies how to translate design models to code.

  • Different parts: components, classes, attributes, operations, state charts, etc.

  • A correspondence table between UML types and the target language types.

  • Occasionally: a specific UML profile (set of tags and stereotypes), generation templates, configurations, etc.

Implementation Roadmap

For each component:
  • Implement component

  • Implement unit test

  • For each class in a component:

    • Implement class

    • Implement unit tests

Plan

  • Introduction

  • Implementation Strategy

  • Classes

  • Attributes

  • Associations

Creating an Implementation Strategy

An implementation strategy must contain rules for:
  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

Rules for Class Implementation

Class Implementation

Different approaches:

  • Simple correspondence.

    • For each UML class, create a Java class.

  • Class-Interface.

    • For each UML class, create a pair (class, interface)

  • Generation Gap Pattern.

    • For each UML class, create a triple (interface, abstract class, concrete class)

Simple Correspondence Approach

For each UML class, create a Java class.

HTMLPage
public class HTMLPage  {
    // (...)
}

Class-Interface Approach

For each UML class, create a pair (class, interface):

HTMLPage Interface

Here, the «Implementation Model» is a graphical representation for Java.

Class-Interface Approach (Cont.)

HTMLPage

For each UML class, create a pair (class, interface):

public interface HTMLPage {
    // (...)
}

public class BasicHTMLPage implements HTMLPage {
    // (...)
}

Class-Interface Approach Wrap-up

  • Useful when the class HTMLPage is used in different contexts: DAO, RPC, Persistence, Tests, etc.

Generation Gap Approach

  • Based on the «Generation Gap» pattern.

Generation Gap Approach

For each UML class, create a triple (interface, abstract class, concrete class):

Generation Gap
HTMLPage

For each UML class, create a triple (interface, abstract class, concrete class):

public interface HTMLPage {
    // (...)
}

public abstract class BasicHTMLPage implements HTMLPage {
    // (...)
}

public class UserHTMLPage extends BasicHTMLPage {
    // (...)
}

Generation Gap Wrap-up

  • Useful in a automatic code generation context:

    • The subclass UserHTMLPage is only generated once.

    • User code is never overwritten.

  • Class/Interface proliferation.

Approach extension

Use a common interface:

Diagram
public interface Common {
	Common copy();
	Common deepCopy();
	boolean equals(Common);
	String toString();
}

Common Interface

Benefits:
  • Facility methods available for all objects.

  • Can be extended with reflexion methods, e.g.:

    • Object get(String attrName)

    • void call(String methodName)

    • etc.

Implementation Strategy Rules

  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

Type Correspondence Table

UML Types

Base UML only has 5 primitive types. However, new datatypes can be added using «profiles».

TypeValues

Integer

-1, 0, 1, 2, …

Boolean

true, false

UnlimitedNatural

0, 1, *

String

"to be or not to be"

Real

1.5, 3.14, …

Correspondence Table (example)

UMLJavaMySQLTypeScript

Integer

java.lang.Integer

BIGINT

number

Boolean

java.lang.Boolean

BOOLEAN

boolean

UnlimitedNatural

java.lang.Integer

TINYINT

number

String

java.lang.String

VARCHAR

string

Real

java.lang.Double

REAL

number

Another Correspondence Table

UMLJava

Integer

int

Boolean

boolean

UnlimitedNatural

int

String

java.lang.StringBuilder

Real

double


Implementation Strategy Rules

  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

Open

UML Attributes

Attributes are a typed structural property, which specify the structure of all instances of a given classifier.

HTMLPage

Attributes are mono- or multi-valued

person
code : Integer [1]
-- monovalued mandatory attribute.
-- e.g.: 1; 2; 99.

last name : String [0..1]
-- monovalued optional attribute.
-- e.g.: null;  "john"; "paul".

first names : String [*]
-- multivalued attribute.
-- e.g.: {}, {"john", "paul"}, {"ringo", "george"}

Attributes have properties

person properties
Properties: readOnly, union, subsets <p>, redefines <p>,
    ordered, unordered, unique, nonunique, seq, sequence, id

Attributes have visibilities

SymbolVisibility

+

Public

#

Protected

~

Package

-

Private

Attributes have constraints

person constraints

Constraints respect the OCL syntax.

Attributes can be derived from others

person age

Derived attributes are specified in OCL.

close parenthesis

Mono-valued Attribute Implementation

Different approaches:
  1. Naive implementation.

  2. Getters and Setters.

  3. Attribute Wrappers.

Naive approach

htmlpage
Rationale:
  • UML Attribute = Java Field

  • Same visibility

  • Read-only attribute = final field

Naive approach (Cont.)

htmlpage
public class HTMLPage {
	public final String title;
	Integer version;
	protected String contents;
	private Boolean visibility = new Boolean(true);
}

Naive Approach Drawbacks

htmlpage
  • Cannot handle:

    • Derived attributes.

    • Constraints.

Getters/Setter approach

htmlpage
Rationale:
  • For each mono-valued attribute:

    • Create a private field for each non-derived attribute.

    • Create a getter method for each attribute, respecting the visibility.

    • Create a setter method for each non read-only, non derived attribute.

Create a private filed for each non-derived attribute.

htmlpage
public class HTMLPage {
	private final String title;
	private Integer version;
	private String contents;
	private Boolean visibility = new Boolean(true);
}

Create a getter method for each attribute, respecting the visibility.

HTMLPage
public class HTMLPage {
    public String getTitle() {
        return title;
    }
    public Integer getSize() {
        return contents.size();
    }
    Integer getVersion() {
        return version;
    }
    protected String getContents() {
        return contents;
    }
    private Boolean getVisibility() {
        return visibility;
    }
}

Create a setter method for each non read-only, non derived attribute.

htmlpage
public class HTMLPage {
    void setVersion(Integer aVersion) {
        version = aVersion;
    }
    protected void setContents(String str) {
        contents = str;
    }
    private void setVisibility(Boolean bool) {
        visibility = bool;
    }
}

Getter/Setter Approach Wrap-up

  • All fields are private.

  • Visibility is ensured by method access.

  • Getters and Setters can implement read-only and derived attributes.

Attribute Wrapper Approach

htmlpage
Rationale:
  • Create a wrapper class for attribute types.

  • For each mono-valued attribute:

    • Create a private field for each non-derived attribute.

    • Create an accessor method for each attribute, respecting the visibility.

Create a wrapper class for attribute types

htmlpage
public class Attribute<T> {
    private T value;
    public Attribute();
    public Attribute(T t) {
        value = t;
    }
    public void set(T newValue) {
        value = newValue;
    }
    public T get() {
        return value;
    }
}

Read-only attributes

htmlpage
public class ReadOnlyAttribute<T> {
    private final T value;
    public Attribute(T t) {
        value = t;
    }
    public void set(T newValue) {
        throw new UnsupportedOperationException();
    }
    public T get() {
        return value;
    }
}

Derived attributes

HTMLPage
public class SizeAttribute<T> {
    private final Attribute<String> contents;

    public SizeAttribute(Attribute<String> attr) {
        contents = attr;
    }
    public void set(T newValue) {
        throw new UnsupportedOperationException();
    }
    public T get() {
        return contents.get().size();
    }
}

Create a private filed for each non-derived attribute.

HTMLPage
public class HTMLPage {
    private final Attribute<String> title =
        new ReadOnlyAttribute<String>();
    private final Attribute<Integer> version =
        new Attribute<Integer>();
    private final Attribute<String> contents =
        new Attribute<String>();
    private final Attribute<Boolean> visibility =
        new Attribute<Boolean>(true);
    private final SizeAttribute<Integer> size =
        new SizeAttribute(contents);
}

Create an accessor method for each attribute, respecting the visibility.

HTMLPage
public class HTMLPage {
    public Attribute<String> title() {
       return title;
    }
    public SizeAttribute<Integer> size() {
        return size;
    }
    Attribute<Integer> version() {
        return version;
    }
    protected Attribute<String> contents() {
        return contents;
    }
    private Attribute<Boolean> visibility() {
        return visibility;}}

Attribute Wrapper Approach Wrap-up

  • All fields are private.

  • Visibility is ensured by method access.

  • Class/object multiplication.

  • Wrappers can implement read-only and derived attributes, but a specific class may be necessary.

  • Extensible: other methods/behaviors can be implemented, e.g. reset()

Implementation Strategy Rules

  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

Open

Multi-Valued Attributes have properties

Patient
Close

Multi-valued Attribute Implementation

Different approaches:
  1. Naive implementation.

  2. Getters and Setters.

  3. Attribute Wrappers.

Naive approach

Rationale:
  • UML Attribute = Java Field

  • Same visibility

  • Relay on the Java Collections Framework, JCF

Patient

Naive approach (Cont.)

Naive approach
public class Patient {
    public final Set<String> pathologies =
        new HashSet<String>;
    public final Collection<String>  exams =
        new ArrayList<String>();
    public final List<Double> temperatures =
        new ArrayList<Double>;
    public final Collection<String> notes =
        new ArrayList<String>();
}

Naive Approach Drawbacks

Naive approach
Cannot handle:
  • Multiplicities.

  • Constraints.

Getters/Setter approach

Naive approach
Rationale:
  • For each multi-valued attribute:

    • Create a private field for each non-derived attribute.

    • Create add(), remove(), and get() methods for each attribute, respecting the visibility.

Create a private field for each non-derived attribute.

Naive approach
public class Patient {
    private final Set<String> pathologies =
        new HashSet<String>();
    private final Collection<String>  exams =
        new ArrayList<String>();
    private final List<Double> temperatures =
        new ArrayList<Double>();
    private final Collection<String> notes =
        new ArrayList<String>();
}

Create a «add()» method for each attribute

Naive approach
public class Patient {
    public boolean addPathologie(String str) {
        return this.pathologies.add(str);
    }
    public boolean addExam(String str) {
        return this.examns.add(str);
    }
    public boolean addTemperature(Double d) {
        return this.temperatures.add(d);
    }
    public boolean addNote(String str) {
        if (notes.size == 5) return false;

        return this.notes.add(str);
    }
}

Create a «remove()» method for each attribute

patient
public class Patient {
    public boolean removePathologie(String str) {
        return this.pathologies.remove(str);
    }
    public boolean removeExam(String str) {
        return this.examns.remove(str);
    }
    public boolean removeTemperature(Double d) {
        return this.temperatures.remove(d);
    }
    public boolean removeNote(String str) {
        return this.notes.remove(str);
    }
}

Create a «get()» method for each attribute

patient
public class Patient {
	public String getPathologie(int i) {
		return this.pathologies.get(i);
	}
	public String getExam(int i) {
		return this.examns.get(i);
	}
	public Double getTemperature(int i) {
		return this.temperatures.get(i);
	}
	public String getNote(int i) {
		return this.notes.get(i);
	}
}

Getter/Setter Approach Wrap-up

  • All fields are private.

  • Visibility is ensured by method access.

  • Getters and Setters can implement maximum multiplicity checks.

  • Limited interface for dealing with collections: only 3 methods vs. 25 for Java List interface.

  • Method proliferation.

Attribute Wrapper Approach

patient
Rationale:
  • Create a wrapper class for attribute types.

  • For each multi-valued attribute:

    • Create a private field for each attribute.

    • Create an accessor method for each attribute, respecting the visibility.

Create a wrapper class for attribute types

patient
public class MultivaluedAttribute<T> {
    private final List<T> values;

    public MultivaluedAttribute(List<T> l) {
        this.valued = l;
    }

    public boolean add(T t) {
        return this.values.add(t);
    }

    public boolean remove(T t) {
        return this.values.remove(t);
    }

    // (...)
}

Create a private field for each attribute

patient
public class Patient {
    private final MultivaluedAttribute<String> pathologies =
        new MultivaluedAttribute<String>(new HashSet<String>());

    private final MultivaluedAttribute<String>  exams =
        new MultivaluedAttribute<String>(new ArrayList<String>());

    private final MultivaluedAttribute<Double> temperatures =
        new MultivaluedAttribute<double>(new ArrayList<Double>());

    private final MultivaluedAttribute<String> notes =
        new MultivaluedAttribute<String>(new ArrayList<String>());
}

Create an accessor method for each attribute, respecting the visibility.

patient
public class Patient {
    public MultivaluedAttribute<String> pathologies() {
        return this.pathologies;
    }
    public MultivaluedAttribute<String> exams() {
        return this.examns;
    }
    public MultivaluedAttribute<Double> temperature() {
        return this.temperatures;
    }
    public MultivaluedAttribute<String> notes() {
        return this.notes;
    }
}

Attribute Wrapper Approach Wrap-up

  • All fields are private.

  • Visibility is ensured by method access.

  • Class/object proliferation.

  • Wrappers can implement read-only and derived attributes, but a specific class may be necessary.

  • Wrappers can implement maximum multiplicity checks.

  • Extensible: other methods/behaviors can be implemented, e.g. the Java List interface.

Implementation Strategy Rules

  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

Open

UML Associations

  • An association between two (or more) classes represents a stable link between two (or more) objects, instances from theses classes.

uml association terms

Associations have directions

uml association directions

Associations have properties

uml association properties
close parenthesis

Unidirectional Associations

Different approaches:
  1. Getters and Setters.

  2. Cursors.

Getters and Setters

client account card
Rationale
  • Monovalued roles ([0..1], [1]): similar to attributes.

  • Multivalued roles ([0..2], [*], etc.): use the Collection interface:

  • Visibility is ensured by accessor visibilities.

Getters and Setters (Cont.)

card account
public class Card {
    private Account account;

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account anAccount) {
        this.account = anAccount;
    }
}

Getters and Setters (Cont.)

Diagram
public class HTMLFolder {

    private Collection<HTMLPage> pages =
        new PageCollection(new HashSet<HTMLPage>());

    public Collection<HTMLPage> getPages() {
        return pages;
    }
}

Getters and Setters (Cont.)

public class PageCollection implements Collection<HTMLPage> {

    private Collection<HTMLPage> pages;

    public PageCollection(Collection<HTMLPage> list) {
        this.pages = list;
    }

    public int size() {
        return pages.size();
    }

    public boolean isEmpty() {
        return pages.isEmpty();
    }

    public boolean contains(Object o) {
        return pages.contains(o);
    }

    public Iterator<HTMLPage> iterator() {
        return pages.iterator();
    }

    public Object[] toArray() {
        return pages.toArray();
    }

    public <T> T[] toArray(T[] a) {
        return pages.toArray(a);
    }

    public boolean add(HTMLPage htmlPage) {
        return pages.add(htmlPage);
    }

    public boolean remove(Object o) {
        return pages.remove(o);
    }

    public boolean containsAll(Collection<?> c) {
        return pages.containsAll(c);
    }

    public boolean addAll(Collection<? extends HTMLPage> c) {
        return pages.addAll(c);
    }

    public boolean addAll(int index, Collection<? extends HTMLPage> c) {
        return pages.addAll(index, c);
    }

    public boolean removeAll(Collection<?> c) {
        return pages.removeAll(c);
    }

    public boolean retainAll(Collection<?> c) {
        return pages.retainAll(c);
    }

    public void clear() {
        pages.clear();
    }

    public HTMLPage get(int index) {
        return get(index);
    }

    public HTMLPage set(int index, HTMLPage element) {
        return set(index, element);
    }

    public boolean remove(int index) {
        return pages.remove(index);
    }

    public int lastIndexOf(Object o) {
        return lastIndexOf(o);
    }

}

Getter/Setter Approach Wrap-up

  • Mono- and multi-valued roles have different interfaces.

  • The Collection decorator allows the addition of new behavior:

    • Upper-bound multiplicity check.

    • Constraints.

  • Unique roles may use the JCF Set implementations (HashSet, TreeSet, etc.).

«Cursors» Approach

  • Rationale:

  • Mono- and multi-valued roles use the same interface, the Cursor.

  • Cursors are specific to each role.

cursor

«Cursors» Approach

folder files
file cursor

«Cursors» Approach

folder files
public interface Folder {

    String getName();
    void setName(String aName);

    FileCursor files();
}

«Cursors» Approach

folder files
public interface File {

    Integer getSize();
    void setSize(Integer aSize);

    String getName();
    void setName(String aName);
}

FileCursor

public class FileCursor implements Cursor<File>, File {

    Collection<File> files;
    Optional<File> current;

    public FileCursor(Collection<File> aCollection) {
        files = aCollection;
    }

    @Override
    public Integer getSize() {
        if (current.isPresent()) {
            return current.get().getSize();
        } else {
            throw new IllegalStateException();
        }
    }

    @Override
    public void setSize(Integer aSize) {
        if(!current.isPresent()) {
            throw new IllegalStateException();
        }
        current.get().setSize(aSize);
    }

    @Override
    public String getName() {
        if (current.isPresent()) {
            return current.get().getName();
        } else {
            throw new IllegalStateException();
        }
    }

    @Override
    public void setName(String aName) {
        if(!current.isPresent()) {
            throw new IllegalStateException();
        }
        current.get().setName(aName);
    }

    @Override
    public boolean valid() {
        return false;
    }

    @Override
    public void next() {

    }

    @Override
    public void remove() {

    }

    @Override
    public void insert(File file) {

    }
}

Cursors Approach Wrap-up

  • Mono- and multi-valued roles have the same interface.

    • Is it a good thing?

  • Cursors have less methods than Collections.

  • Cursors allow the addition of new behavior:

    • Upper-bound multiplicity check.

    • Constraints.

  • Unique roles may use the JCF Set implementations (HashSet, TreeSet, etc.).

  • Respects the «Demeter» law.

Implementation Strategy Rules

  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

Open

Handshaking

folder files bi
Referential integrity
  • A file cannot belong to two folders at the same time.

  • Adding a file to a folder should have the same effect as setting the folder of a file.

Handshaking (Cont.)

Handshake
doc.setFolder(archive)
archive.getFiles().add(doc)
snapshot2
snapshot3
close parenthesis

Bidirectional Associations

Different approaches:
  1. Getters and Setters.

  2. Cursors.

Bidirectional Associations

Simple Case

folder files bi readonly

Simple Case (Cont.)

folder files bi readonly
public class File {
    private Folder folder;

    public File(Folder aFolder) {
        folder = aFolder;
    }

    public Folder getFolder() {
        return folder;
    }
}

Bidirectional Associations

folder files bi
To ensure the integrity:
  • file.setFolder(folder) must call folder.getFiles().add(file) and

  • folder.getFiles().add(file) must call file.setFolder(folder)

  • Problem: How to avoid the loop?

Solution 1: let the client call both

Folder folder = new Folder();
File file = new File();

file.setFolder(folder);
folder.getFiles().add(file);

Problems

  • All clients must respect this rule.

  • Difficult to ensure during the class lifetime.

Solution 2: use auxiliary methods

  • Add a method called basicSet(Folder) to the class File.

  • Add a method called basicAdd(File) to the class FileCollection.

sd add file
sd set folder

More problems

  • Getter/Setter approach: basicAdd() is not in the List interface.

  • Cursor approach: each cursor must know its opposite (not so simple).

Implementation Strategy Rules

  1. Class implementation.

  2. Type correspondence.

  3. Mono-valued attribute implementation.

  4. Multi-valued attribute implementation.

  5. Unidirectional association implementation.

  6. Bidirectional association implementation.

  7. Operation implementation.

open parenthesis

Operations, Receptions, and Methods

  • An Operation is a feature of a class that specifies the name, type, parameters, and constraints for invoking an associated behavior.

  • A Reception specifies that a class is prepared to receive a Signal.

  • Methods can implement both, operations and receptions. Method execution is either synchronous, or asynchronous.

Receptions and Signals

receptions

Operations

operations
close parenthesis

Operation implementation

operations
public class Book {
    public boolean reserve(Reader aReader) {
        // TODO
        return false;
    }

    public void deliver() {
        // TODO
    }

    public void borrow() {
        // TODO
    }

    public void returnBook() {
        // TODO
    }
}

Reception implementation

receptions
public class Notify implements Serializable {

    public final String message;

    public Notify(String message) {
        this.message = message;
    }
}

Reception implementation (Cont.)

receptions
public class Alarm {

    private final BlockingQueue<Notify> notifications =
        new ArrayBlockingQueue<Notify>(10);

    public void accept(Notify notify) {
        this.notifications.offer(notify);
    }

    // TODO: write a thread that reads the blocking queue and executes the notification.
}

Conclusion

  • UML is a rich modeling language with many interesting features.

  • There are several possible ways to translate designs to code.

  • We presented different implementation strategies.