Detailed Table of Contents
Guidance for the item(s) below:
Good news: this will the the last installment of UML notations.
Bad news: we are going to cover an entire new diagram type in one go (reason: to give you more time to use them in project documentation).
Can draw basic sequence diagrams
Sequence diagrams model the interactions between various entities in a system, in a specific scenario. Modelling such scenarios is useful, for example, to verify the design of the internal interactions is able to provide the expected outcomes.
Some examples where a sequence diagram can be used:
To model how components of a system interact with each other to respond to a user action.
To model how objects inside a component interact with each other to respond to a method call it received from another component.
UML Sequence Diagrams → Introduction
UML Sequence Diagrams → Basic Notation
UML Sequence Diagrams → Loops
UML Sequence Diagrams → Object Creation
UML Sequence Diagrams → Minimal Notation
Can draw intermediate-level sequence diagrams
UML Sequence Diagrams → Object Deletion
UML Sequence Diagrams → Self-Invocation
UML Sequence Diagrams → Alternative Paths
UML Sequence Diagrams → Optional Paths
UML Sequence Diagrams → Calls to Static Methods
Can interpret sequence diagrams with parallel paths
UML uses par
frames to indicate parallel paths.
Notation:
Logic
is calling methods CloudServer#poll()
and LocalData#poll()
in parallel.
If you show parallel paths in a sequence diagram, the corresponding Java implementation is likely to be multi-threaded because a normal Java program cannot do multiple things at the same time.
Guidance for the item(s) below:
Previously, you learned:
This week, we cover design patterns, a concept that builds upon the above. Again, we limit to only two of them, for similar reasons.
Guidance for the item(s) below:
First, let's learn what design patterns are, in general.
Can explain design patterns
Design pattern: An elegant reusable solution to a commonly recurring problem within a given context in software design.
In software development, there are certain problems that recur in a certain context.
Some examples of recurring design problems:
Design Context | Recurring Problem |
---|---|
Assembling a system that makes use of other existing systems implemented using different technologies | What is the best architecture? |
UI needs to be updated when the data in the application backend changes | How to initiate an update to the UI when data changes without coupling the backend to the UI? |
After repeated attempts at solving such problems, better solutions are discovered and refined over time. These solutions are known as design patterns, a term popularized by the seminal book Design Patterns: Elements of Reusable Object-Oriented Software by the so-called "Gang of Four" (GoF) written by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
Can explain design patterns format
The common format to describe a pattern consists of the following components:
Guidance for the item(s) below:
Now that you know what design pattern is, let's learn a few example design patterns.
Can explain the Singleton design pattern
Context
Certain classes should have no more than just one instance (e.g. the main controller class of the system). These single instances are commonly known as singletons.
Problem
A normal class can be instantiated multiple times by invoking the constructor.
Solution
Make the constructor of the singleton class private
, because a public
constructor will allow others to instantiate the class at will. Provide a public
class-level method to access the single instance.
Example:
The <<Singleton>>
in the class above uses the UML stereotype notation, which is used to (optionally) indicate the purpose or the role played by a UML element. In this example, the class Logic
is playing the role of a Singleton class. The general format is <<role/purpose>>
.
Can apply the Singleton design pattern
Here is the typical implementation of how the Singleton pattern is applied to a class:
class Logic {
private static Logic theOne = null;
private Logic() {
...
}
public static Logic getInstance() {
if (theOne == null) {
theOne = new Logic();
}
return theOne;
}
}
Notes:
private
, which prevents instantiation from outside the class.private
class-level variable.public
class-level operation getInstance()
which instantiates a single copy of the singleton class when it is executed for the first time. Subsequent calls to this operation return the single instance of the class.If Logic
was not a Singleton class, an object is created like this:
Logic m = new Logic();
But now, the Logic
object needs to be accessed like this:
Logic m = Logic.getInstance();
Can decide when to apply Singleton design pattern
Pros:
Cons:
Given that there are some significant cons, it is recommended that you apply the Singleton pattern when, in addition to requiring only one instance of a class, there is a risk of creating multiple objects by mistake, and creating such multiple objects has real negative consequences.
Can explain the Facade design pattern
Context
Components need to access functionality deep inside other components.
The UI
component of a Library
system might want to access functionality of the Book
class contained inside the Logic
component.
Problem
Access to the component should be allowed without exposing its internal details. e.g. the UI
component should access the functionality of the Logic
component without knowing that it contains a Book
class within it.
Solution
Include a class that sits between the component internals and users of the component such that all access to the component happens through the Facade class.
The following class diagram applies the Facade pattern to the Library System
example. The LibraryLogic
class is the Facade class.
Follow up notes for the item(s) above:
To learn more design patterns, you can refer to https://se-education.org/se-book/designPatterns/
Guidance for the item(s) below:
Previously, you learned how to write JUnit tests. How do you know which parts of the code is being tested by your tests? That's where test coverage comes in.
Can explain test coverage
Test coverage is a metric used to measure the extent to which testing exercises the code i.e., how much of the code is 'covered' by the tests.
Here are some examples of different coverage criteria:
if
statement evaluated to both true
and false
with separate test cases during testing is considered 'covered'. if(x > 2 && x < 44)
is considered one decision point but two conditions.
For 100% branch or decision coverage, two test cases are required:
(x > 2 && x < 44) == true
: [e.g. x == 4
](x > 2 && x < 44) == false
: [e.g. x == 100
]For 100% condition coverage, three test cases are required:
(x > 2) == true
, (x < 44) == true
: [e.g. x == 4
] [see note 1](x < 44) == false
: [e.g. x == 100
](x > 2) == false
: [e.g. x == 0
]Note 1: A case where both conditions are true
is needed because most execution environments use a short circuiting behavior for compound boolean expressions e.g., given an expression c1 && c2
, c2
will not be evaluated if c1
is false
(as the final result is going to be false
anyway).
Guidance for the item(s) below:
Learn how to measure test coverage in your tP. You will be asked to demo that in the coming tutorial.
Can explain how test coverage works
Measuring coverage is often done using coverage analysis tools. Most IDEs have inbuilt support for measuring test coverage, or at least have plugins that can measure test coverage.
Coverage analysis can be useful in improving the quality of testing e.g., if a set of test cases does not achieve 100% branch coverage, more test cases can be added to cover missed branches.
Measuring code coverage in Intellij IDEA (watch from 4 minutes 50 seconds
mark)