Detailed Table of Contents
Introduction
[W9.1a] Design → Design Patterns → Introduction → What :
[W9.1b] Design → Design Patterns → Introduction → Format :
Singleton pattern
[W9.1c] Design → Design Patterns → Singleton → What :
[W9.1d] Design → Design Patterns → Singleton → Implementation :
[W9.1e] Design → Design Patterns → Singleton → Evaluation
Facade pattern
Guidance for the item(s) below:
Previously, you learned:
This week, we cover design patterns, a concept that builds upon the above.
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.
Exercises
Definition of design patterns
Which one of these describes the ‘software design patterns’ concept best?
(b)
Can explain design patterns format
The common format to describe a pattern consists of the following components:
Exercises
Anti-patterns required?
When we describe a pattern, we must also specify anti-patterns.
False.
Explanation: Anti-patterns are related to patterns, but they are not a ‘must have’ component of a pattern description.
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:
Exercises
Statements about the Singleton pattern
You use the Singleton pattern when
(c)
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 a French word that means 'front of a building'Façade 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.
Exercises
Is this Facade?
Is the design below likely to use the Facade pattern?
True.
Facade is clearly visible (Storage is the <<Facade>>
class).
Guidance for the item(s) below:
Given next are two techniques that help you locate problems in the code: logging, and assertions
Can explain logging
Logging is the deliberate recording of certain information during a program execution for future reference. Logs are typically written to a log file but it is also possible to log information in other ways e.g. into a database or a remote server.
Logging can be useful for troubleshooting problems. A good logging system records some system information regularly. When bad things happen to a system e.g. an unanticipated failure, their associated log files may provide indications of what went wrong and actions can then be taken to prevent it from happening again.
A log file is like the flight data recorderblack box of an airplane; they don't prevent problems but they can be helpful in understanding what went wrong after the fact.
source: https://commons.wikimedia.org
Exercises
Logging vs blackbox
Why is logging like having the 'black box' in an airplane?
(a)
Can use logging
Most programming environments come with logging systems that allow sophisticated forms of logging. They have features such as the ability to enable and disable logging easily or to change the logging how much information to recordintensity.
This sample Java code uses Java’s default logging mechanism.
First, import the relevant Java package:
import java.util.logging.*;
Next, create a Logger
:
private static Logger logger = Logger.getLogger("Foo");
Now, you can use the Logger
object to log information. Note the use of a INFO
, WARNING
etc.logging level for each message. When running the code, the logging level can be set to WARNING
so that log messages specified as having INFO
level (which is a lower level than WARNING
) will not be written to the log file at all.
// log a message at INFO level
logger.log(Level.INFO, "going to start processing");
// ...
processInput();
if (error) {
// log a message at WARNING level
logger.log(Level.WARNING, "processing error", ex);
}
// ...
logger.log(Level.INFO, "end of processing");
Resources
Tutorials:
Best Practices:
Can explain assertions
Assertions are used to define assumptions about the program state so that the runtime can verify them. An assertion failure indicates a possible bug in the code because the code has resulted in a program state that violates an assumption about how the code should behave.
An assertion can be used to express something like when the execution comes to this point, the variable v
cannot be null.
If the runtime detects an assertion failure, it typically takes some drastic action such as terminating the execution with an error message. This is because an assertion failure indicates a possible bug and the sooner the execution stops, the safer it is.
In the Java code below, suppose you set an assertion that timeout
returned by Config.getTimeout()
is greater than 0
. Now, if Config.getTimeout()
returns -1
in a specific execution of this line, the runtime can detect it as an assertion failure -- i.e. an assumption about the expected behavior of the code turned out to be wrong which could potentially be the result of a bug -- and take some drastic action such as terminating the execution.
int timeout = Config.getTimeout();
Can use assertions
Use the assert
keyword to define assertions.
This assertion will fail with the message x should be 0
if x
is not 0 at this point.
x = getX();
assert x == 0 : "x should be 0";
...
Assertions can be disabled without modifying the code.
java -enableassertions HelloWorld
(or java -ea HelloWorld
) will run HelloWorld
with assertions enabled while java -disableassertions HelloWorld
will run it without verifying assertions.
Java disables assertions by default. This could create a situation where you think all assertions are being verified as true
while in fact they are not being verified at all. Therefore, remember to enable assertions when you run the program if you want them to be in effect.
Enable assertions in Intellij (how?) and get an assertion to fail temporarily (e.g. insert an assert false
into the code temporarily) to confirm assertions are being verified.
Java assert
vs JUnit assertions: They are similar in purpose but JUnit assertions are more powerful and customized for testing. In addition, JUnit assertions are not disabled by default. We recommend you use JUnit assertions in test code and Java assert
in functional code.
Resources
Tutorials:
Best practices:
Can use assertions optimally
It is recommended that assertions be used liberally in the code. Their impact on performance is considered low and worth the additional safety they provide.
Do not use assertions to do work because assertions can be disabled. If not, your program will stop working when assertions are not enabled.
The code below will not invoke the writeFile()
method when assertions are disabled. If that method is performing some work that is necessary for your program, your program will not work correctly when assertions are disabled.
...
assert writeFile() : "File writing is supposed to return true";
Assertions are suitable for verifying assumptions about Internal Invariants, Control-Flow Invariants, Preconditions, Postconditions, and Class Invariants. Refer to [Programming with Assertions (second half)] to learn more.
Exceptions and assertions are two complementary ways of handling errors in software but they serve different purposes. Therefore, both assertions and exceptions should be used in code.
Exercises
Assertion failure in Calculator
A Calculator program crashes with an ‘assertion failure’ message when you try to find the square root of a negative number.
(c)
Explanation: An assertion failure indicates a bug in the code. (b) is not acceptable because of the word "terminated". The application should not fail at all for this input. But it could have used an exception to handle the situation internally.
Statement about exceptions and assertions
Which statements are correct?
(a)
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
](x < 44) == false
: [e.g. x == 100
](x > 2) == false
: [e.g. x == 0
]Exercises
Highest intensity coverage
Which of these gives us the highest intensity of testing?
(b)
Explanation: 100% path coverage implies all possible execution paths through the SUT have been tested. This is essentially ‘exhaustive testing’. While this is very hard to achieve for a non-trivial SUT, it technically gives us the highest intensity of testing. If all tests pass at 100% path coverage, the SUT code can be considered ‘bug free’. However, note that path coverage does not include paths that are missing from the code altogether because the programmer left them out by mistake.
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