Instead of returning null and forcing the caller to check if (x != null), return a "null object" that implements the same interface with no-op behavior.
// ─── EXAMPLE 1 ──────────────────────────────────────────────────────────────
// WHAT WE ARE IMPLEMENTING:
// A telemetry pipeline using safe, do-nothing fallbacks to prevent manual
// null-checks.
//
// WHERE THE NULL OBJECT FITS IN:
// Logger is the abstraction. RealLogger is the active logging component.
// NullLogger acts as the Null Object acting as a safe, silent NOP.
// ────────────────────────────────────────────────────────────────────────────
// --- Interface ---
interface Logger {
void log(String message);
}
// --- Real ---
class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println(" [LOG] " + message);
}
}
// --- Null Object ---
class NullLogger implements Logger {
public void log(String message) {
// No-op — silently discard
}
}
// --- Client that never checks for null ---
class ReportService {
private final Logger logger;
public ReportService(Logger logger) {
this.logger = logger;
}
public void generateReport() {
// Business logic...
logger.log("Report generated");
}
}
public class Main {
public static void main(String[] args) {
// With logging
new ReportService(new ConsoleLogger()).generateReport();
// Without logging — pass NullLogger, no null checks needed
new ReportService(new NullLogger()).generateReport();
}
}