SwadhyAI Notes

All Low-Level Design notes

Extra pattern · not GoF

Null Object

A is a real do-nothing implementation of the same interface as the real collaborator. Callers always call apply() or log(); they never branch on null for “no policy.”

Example: at exit, the fee engine asks for a discount. Most cars have none. If you return null, every caller writes if (discount != null). Miss one check and the gate throws. A NoDiscount object that returns the fee unchanged is a valid answer.

Is “do nothing” a missing value, or a valid behavior I can call?

Absence is a policy

  • No coupon — NoDiscount
  • No extra fee — ZeroSurcharge
  • Quiet logger in tests — NoOpLogger
  • is a valid apply()

Absence is information

  • Missing ticket — Optional or error
  • Unknown user — do not invent a ghost
  • Failed lookup — fail loudly
  • A is not a no-op

No discount is still a discount

java
interface Discount {
  Money apply(Money fee);
}

final class PercentOff implements Discount {
  private final int percent;
  public Money apply(Money fee) { return fee.percentOff(percent); }
}

final class NoDiscount implements Discount {
  public Money apply(Money fee) { return fee; }
}

class Pricing {
  Money quote(Ticket ticket, Discount discount) {
    return discount.apply(hourlyFee(ticket));
  }
}

Pricing does not know whether the car had a coupon. It always has a Discount. That is the same seam as Strategy: swap the implementation, keep the call site clean.

Decision

Optional vs Null Object

QuestionOptionalNull Object
Must the caller notice absence?Yes — orElseThrow, map, empty branchNo — just call the method
Is “do nothing” a legal policy?Usually noYes — NoDiscount, QuietLogger
Missing identity?This is the toolNever — hides bugs
Factory return when code is unknownempty() if unknown is allowedNoDiscount — or throw if illegal

See nullability for the API-level story. This page is the object-model story: a type that is safe to call.

The factory must not return null

If DiscountFactory returns null when the code is unknown, callers keep the null checks. The factory should return NoDiscount — or throw if the code is illegal. That is the same instinct as Factory Method: one place decides the concrete type, including the empty one.

java
class DiscountFactory {
  Discount from(String code) {
    if (code == null || code.isBlank()) return new NoDiscount();
    return percents.getOrDefault(code, new NoDiscount());
  }
}

// Wrong: callers will write if (d != null) again.
// Discount from(String code) { return percents.get(code); }

Cousins

Strategy

Null Object is often a Strategy that happens to do nothing. If you will swap hourly vs airport pricing, say Strategy. If the only other implementation is “no coupon,” Null Object is the honest name.

Special Case (Fowler)

A Customer with no address can be an UnknownCustomer that still answers name() with “guest.” That is a richer Null Object. It is still wrong if you need to know the customer was missing so you can refuse the sale.

Typed errors

Error handling is for failures. Null Object is for success that happens to be empty. Do not return NoDiscount when the coupon code is malformed and you should reject the request.

Null Object for a missing entity

A NullTicket that “exists” so findById never fails will close spots you never opened. Missing identity is Optional or an error, not a silent fake.

Still returning null from the factory

If DiscountFactory returns null when the code is unknown, callers keep the null checks. The factory should return NoDiscount — or throw if the code is illegal.

Zero surcharge is the same shape

Weekend surcharge, EV surcharge, none. Do not return null from SurchargeFactory. Return ZeroSurcharge that adds nothing. Pricing always calls add(). The call site stays one line.

java
interface Surcharge {
  Money addTo(Money fee);
}

final class WeekendBump implements Surcharge {
  public Money addTo(Money fee) { return fee.plus(fee.percent(10)); }
}

final class ZeroSurcharge implements Surcharge {
  public Money addTo(Money fee) { return fee; }
}

A quiet logger is a Null Object

Tests that assert fees should not care about log lines. A NoOpLogger implements Logger and drops every call. Production injects a real logger. That is the same idea as NoDiscount — absence of side effects is a policy.

java
interface LotLogger {
  void info(String message);
}

final class NoOpLogger implements LotLogger {
  public void info(String message) { /* stay quiet */ }
}

class ParkingLot {
  private final LotLogger log;
  ParkingLot(Clock clock, TicketRepository tickets, LotLogger log) {
    this.log = log;
  }
}

Do not return null from a logger factory “when logging is off.” Return NoOpLogger. Otherwise every call site grows a null check, and one missed check throws in production when logging is disabled.

How to explain this in an interview

“Most cars have no coupon. I will make NoDiscount a real Discount so Pricing never branches.” If the interviewer asks about a missing ticket, switch vocabulary: “That is identity. I will return Optional.empty(), not a fake ticket.” That distinction is the page.

Related pages