Educational outcomes
Student:
- explains how to implement constructs such as: different types of constraints in UML (dynamic and static) in natural language (understanding);
- identifies and corrects errors in the source code, which is an implementation of the above structure;
- lists constructs implemented in the source code (understanding);
- implements the above structures using the selected programming language (application);
- indicates functional differences between two selected programs based on code snippets source (analysis);
- creates a program that implements specific business requirements using the constructs learned from the lectures (synthesis);
Justification
Modern programming languages are mostly based on the object paradigm. Therefore, its practical understanding is a necessary basis for working in this profession. Moreover, programmers often make systems based on analytical materials. And these largely use object-oriented graphical notations (such as UML). During this and the next lessons, you will deepen your theoretical bond, but first of all you will learn how to implement different concepts from object-orientness. This will allow you to design and implement modern business applications.
UML extensibility mechanisms
When discussing attribute types, we mentioned a special type of UML expressions called constraints. This is one of the extensibility mechanisms that makes it possible to refine the model. In other words, all the information we are unable to put on the diagram in the "standard" way, we can save as constraints. For this purpose, we can use a special OCL (Object Constraint Language), mathematical expressions (arithmetic, logic), plain text or combinations of these techniques. Regardless of the chosen technique, the content of the constraint is enclosed in curly braces.
Constraints can be divided into dynamic and static.
Implementation of constraints
Unfortunately, in popular programming languages (Java, C#, C++) constraints are not directly present. One way to apply them is to implement them manually in the form of methods. The degree of complexity of such implementations depends on the type of constraint. There are some OCL implementations, but they usually work with model libraries (eg EMF) and therefore we will not be dealing with them.
Implementing constraints is best to assume that the constraint element (eg attribute) is hidden (private) and any modifications are made by dedicated methods (eg settery). The above rule also applies within a class
that has attributes. So having a method in a class that references an attribute in the same class, we should do it using the appropriate getter or setter rather than directly. Unfortunately, this assumption can not be controlled beacuse
even if the attribute is private, then invariants in its class have access to it.
As you can see, this approach requires the use of orthodox encapsulation. The consequence of this decision is the need to write appropriate methods for each of the attributes in the class. Fortunately, modern IDEs support the creation of methods for handling "hidden" attributes. For example, in Eclipse there is a dedicated option in the context menu: Source, Generate Getters and Setters.

We have to decide what to do when the constraint is broken? In the examples below we throw exceptions, but of course this will not always be the best solution. Sometimes it is better to use the assertion mechanism (available in some programming languages) or to display the appropriate message.
Dynamic constraints
In the case of dynamic constraints, the previous condition of the constraint element is important during validation. Examples may be: "{salary can not go down}" or "{salary increase must be smaller than 10%}".
Let's take a look at methods implementing this first constraint. Let's note that the salary attribute is private (2), and any external changes are only possible using the setSalary(float salary) (8) method. This makes
it possible to meet the imposed requirements.
public class Employee {
private float salary;
public float getSalary() {
return salary;
}
public void setSalary(float salary) throws Exception {
// Validate all the constraints
if(salary < this.salary) {
throw new Exception(String.format("The salary (%s) cannot be decreased!%d" salary));
}
if(this.salary * (1d + maxSalaryChangePercentage /100d) < salary) {
throw new Exception(String.format("The salary (%s => %s) increase cannot be more then %s%%" this.salary salary maxSalaryChangePercentage));
}
this.salary = salary;
}
public final static int maxSalaryChangePercentage = 10;
}
Static constraints
Unlike the above case, when checking the static constraint, the previous state of the constraint element is irrelevant, for example, "{salary > 2000,00 PLN}". Here is an example implementation based on similar rules. Note the provided constant (minimalSalary)
- so that any updates will go smoothly.
public class Employee {
private float salary;
public Employee(float salary) {
this.salary = salary; // required direct assignment
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) throws Exception {
// Validate all the constraints
if(salary < minimumSalary) {
throw new Exception(String.format("The new salary (%s) has to be at least %s" salary minimumSalary));
}
this.salary = salary;
}
public final static float minimumSalary = 2000;
public final static float maximumSalary = 5000;
public final static int maxSalaryChangePercentage = 10;
}
Constraint {Unique}
The {unique} constraint is imposed on the attribute and ensures its uniqueness throughout the extent. In other words, the system will not create an object whose attribute value will be duplicated. An example might be PESEL.
For the implementation of {unique} we need to obtain a unique (in extent) value of a particular attribute of the object, eg the mentioned PESEL. This can be done by modifying the "setter" and adding a class method that looks through the whole extent and
verifying that the given value no longer exists. This solution is characterized by quite poor performance. It would be better for each unique attribute to add a class attribute (eg Set) to store all the values
that are already present.
An alternative to this is to add a single map class attribute with the key as an attribute name and the Set as values.
Another way is to use a static Mapbut with the key being a unique attribute and the value of the object from the extent. Thanks to this, we ensure not only the uniqueness of the attribute, but also a quick retrieval of the object
based on the value of the attribute. And that's the common reason for using unique.
Constraint {Subset}
The {subset} constraint can be applied to two associations (or aggregations). Let's look at the figure below. In order to be able to create a link within an association B ("direct"), there must already be a link within association A ("belongs"). Of course, both associations should be between the same classes and both should be between the same objects.

Links within the association described by the roles "consists of","belongs to" we add "normally". By contrast, before adding a link within the association described by the roles "driven by", "direct"we check to see if
there is a link to the object being added to the "parent" association. To automate the whole process, we will add new functionality to the ObjectPlusPlus class. We will create a method that will determine if there is a link
to the given object within a given role. We will put all the functionality of the constraints in a new class called ObjectPlus4. In order not to lose our existing "facilities", it will inherit from ObjectPlusPlus.
Let's start with the method in the ObjectPlusPlus class, which checks for a link to the given object within a given role.
public boolean isLink(String roleName ObjectPlusPlus targetObject) {
Map<Object ObjectPlusPlus> objectLink;
if(!links.containsKey(roleName)) {
// No links for the role
return false;
}
objectLink = links.get(roleName);
return objectLink.containsValue(targetObject);
}
- (4). We check whether we have information about the given role (in the method parameter)
- (6). If not, then we throw an exception.
- (8). At this point in the program, we are sure there is an entry of the links for the given role name. Thanks to that, we get the right collection.
- (10). We are investigating whether the mapping container storing the link information contains a value representing the object being inspected. The result is returned.
The next element that we need to implement is a method that adds a link together with the {subset} constraint. One possible solution is shown in the following listing:
public abstract class ObjectPlus4 extends ObjectPlusPlus {
// [...]
public ObjectPlus4() {
super();
}
public void addLink_subset(String roleName String reverseRoleName String superRoleName ObjectPlusPlus targetObject) throws Exception {
if(isLink(superRoleName targetObject)) {
// There is a (super) link to the added object in the super role
// Create the link
addLink(roleName reverseRoleName targetObject);
}
else {
// No super link ==> exception
throw new Exception("No link to the '" + targetObject + "' object in the '" + superRoleName + "' super role!");
}
}
}
- (2). A constructor that refers in its body to the superclass constructor (we remind you that this is a prerequisite for the proper behavior of objects belonging to classes inherited from
ObjectPlus). - (7). We call a method to check the existence of a link for a given role (discussed earlier).
- (10). We add a link using existing ObjectPlus mechanisms.
- (14). If there is no link within the parent association (the constraint is violated), then we throw an exception.
An example of using the above constructs. We create a worker (1) and a group (2). Then we add parent (5) and subordinate associations using a special method (7). And for validation we display information (10).
var employee = new Employee("John" "Smith");
var group = new Group("Group no 1");
try {
// Add the ordinary link
employee.addLink(roleBelongsTo roleConsistsOf group);
employee.addLink_subset(manages roleManagedBy roleBelongsTo group);
// Show links info
employee.showLinks(roleBelongsTo System.out);
employee.showLinks(manages System.out);
} catch (Exception e) {
// Error
e.printStackTrace();
}
Constraint {Ordered}
Constraint {ordered} may concern:
- Association. In this case, the links are stored (and received and processed) in a certain order.
- Class. In this situation, the objects in the extent are stored (and received and processed) in a certain order.

Implementing the {ordered} constraint is relatively straightforward. All we have to do is ensure that the order of the items stored does not change. To do this, you need to use a specific container type. If we want elements to be stored in the order of
addition, we use eg List. However, when you need specific sorting criteria, you may want to use a container with a comparator (eg TreeSet).
By default, the ObjectPlusPlus class uses the Vector class for extent (guaranteed order) and HashMap for links. In summary, implementation of the {ordered} restriction is fairly straightforward, and
in practice it boils down to the use of a certain type of container (guaranteeing the order of items).
Constraints {Bag} and {History}
A classic association, without any additional markings, does not allow for more than one link between the same objects. If for some reason we require it, we need to apply a {bag} or {history} constraint. They are similar to each other. The difference between them is quite blurry. Both constraints allow for duplicate links, but {history} highlights the aspect of change over time. Typically, this is the case for association with attribute, as shown below:

But as we recall, the implementation of such an association is based on transforming it into two binary associations and an intermediate class:

Let's look at what happened - the problem of the links between the same objects simply disappeared. Instead of a recurring connection between actor and moviewe have links between actor and MovieActor and MovieAktor and actor. Thus we do not need to do anything special. In this case, the {bag} constraint simply could be ignored and normally implements the association. However, it may happen that someone would like to store duplicates, eg to
count how many times and how much someone drank a drink (for Person and Drink classes). In this situation you have to choose a container that allows you to remember the same reference many times, eg Vector. The
ObjectPlusPlus class utilizes the HashMap class for links that guard the uniqueness of keys. You can add duplicate values, but with unique keys. Or you need to modify the design assumptions for the ObjectPlusPlus class. The {history} restriction is implemented the same way. We said earlier that it meant more or less the same as {bag}, but emphasizes the temporal aspect of the information. Consequently, from the implementation point of view, we
do exactly the same thing as for {bag} restrictions.
Constraint {XOR}
The constraint {xor} affects at least two associations. Ensures that there will be only one link within them. Let's look at the example below: If person is working in governmentit can not work with company and vice versa.

What do we need to do to implement this type of a constraint? This case is quite obvious - it is important to ensure that there is only one link in the defined association group (roles). We will show an example solution for the ObjectPlus4 class:
public abstract class ObjectPlus4 extends ObjectPlusPlus {
private List<String> rolesXOR = new LinkedList<>();
// [...]
public void addXorRole(String xorRoleName) {
rolesXOR.add(xorRoleName);
}
public void addLinkXor(String roleName String reverseRoleName ObjectPlusPlus targetObject) throws Exception {
if(rolesXOR.contains(roleName)) {
// The currently adding role is XOR'ed
// Check if there is a link for XOR'ed roles
if(isXorLink()) {
throw new Exception("There is a link for a XOR roles!");
}
// There is no link ==> add a link using an already existing method from a super class
}
// Add the link
super.addLink(roleName reverseRoleName targetObject);
}
private boolean isXorLink() {
for(String role : rolesXOR) {
if(this.anyLink(role)) {
return true;
}
}
return false;
}
}
The modification begins with the addition of a container (2) storing the names of the constraint roles. Then we create a method which adds the given role name (6). This addition means that association within this role is constrained. In the main method of creating the connection (10), we do the following:
- (11). We are checking whether the role being added is {xor} restricted.
- (15). For constrained roles, we check that if there is already a link. If so, we throw an exception. If there are no links, the control goes to the standard method that creates the connection between the objects.
- (23).Calling a "normal" method (from the superclass) that creates a link between objects.
This sample implementation can be supplemented by the support for adding links from the other side.
Custom constraints
In this lesson, we outlined the most common "standard" constraints defined within UML notation. Note, however, that the analyst has complete freedom in formulating constraints and does not need to use only those provided by the UML creators. What to do in that case? For any other constraints, such as "plain text", you must:
- Understand "what the author meant". This part will probably be the most difficult, especially when the restriction is written without the use of formalism, ie, plain text. If in doubt, ask the person who formulated it. It may turn out (for example, at the test stage) that due to misunderstanding of the system requirements it is necessary to make quite significant changes in the code of the program.
- Implement them using the appropriate method.
Sometimes a seemingly minor limitation may even require a change in the approach to program development, such as the use of orthodox encapsulation. And it is important to realize that we will rarely be able to use ready-made solutions.
Source codes

A comparator is a special object whose purpose is to determine the order of the items being compared (it implements the Comparator interface). This allows the sorting method to know in what order to place the objects in the specified container.