Course content
Singleton: Make Logger a Single Shared Instance
Easy·Tagsdesign-patternscreationalsingleton
Problem Statement
The starter `Logger` is a regular class. Every caller that wants a logger calls `new Logger()` and gets its own private instance. Messages logged through one logger are not visible through another, even though every caller thinks it is talking to "the" logger.
The `getInstance` method on the starter looks helpful but is broken. It returns a fresh `Logger` every call, so it does not actually share anything either:
```java
public static Logger getInstance() {
return new Logger();
}
```
Refactor `Logger` into a Singleton: exactly one shared instance, accessible through `Logger.getInstance()`, with no way for anyone to construct their own.
After your refactor, the design must satisfy this contract:
- `Logger.getInstance()` returns the same reference every time it is called.
- `Logger`'s constructor is private. Callers cannot do `new Logger()`.
- Messages logged through one `Logger.getInstance()` reference are visible through any other `Logger.getInstance()` reference, because they all point at the same object.
- `getInstance` is a static method that returns a `Logger`.
The validator runs five checks:
1. `Logger.getInstance() == Logger.getInstance()` evaluates to true. The behavioural Singleton test.
2. `Logger` does not have a public no-arg constructor. Reflection check, mirrors the contract that callers cannot allocate their own.
3. State is shared across `getInstance()` references. The validator logs a message through one reference, retrieves messages through another, and asserts the message is present.
4. `getInstance` is declared and is static.
5. `log` and `getMessages` work correctly when used through a single `Logger.getInstance()` reference. Smoke test against accidental damage to the basic API.
The smelly starter passes scenarios 4 and 5 only. The other three require the actual refactor.
Examples
Example 1
Input
Logger a = Logger.getInstance(); Logger b = Logger.getInstance(); a == bOutput
"true"Why
Both references point at the same shared Logger object.
Example 2
Input
Logger.getInstance().log("booting"); Logger.getInstance().getMessages()Output
"[\"booting\"]"Why
The message logged via one reference is visible through any other reference because there is only one Logger.
Example 3
Input
new Logger()Output
"compile error after the refactor"Why
The constructor is private. The compiler refuses any direct allocation, which is exactly what makes Logger a Singleton.
Constraints
- •Logger must declare a private no-arg constructor.
- •Logger must declare a public static method named getInstance() that returns Logger.
- •Successive calls to Logger.getInstance() must return the same reference.
- •Logger.log(String) must append the message to the shared message list.
- •Logger.getMessages() must return the shared message list.