← Null object lesson
Lab

Null object

Sign in to run your code

Read the brief and the starter here. With a free account you can edit it, run the sample, step through it and check your work, and your progress is kept.

// What the rider's phone played. Checker plumbing: you don't need to edit this.
const felt: string[] = [];

// The phone's vibration, behind one method.
interface Haptics {
  pulse(cue: string): void;
}

// The phone's vibration motor.
class PhoneHaptics implements Haptics {
  pulse(cue: string): void {
    felt.push(cue);
  }
}

// The dock is required: it releases or locks a bike, or throws why it can't.
class MemoryDock {
  private docked: Set<string>;
  constructor(bikes: string[]) {
    this.docked = new Set(bikes);
  }
  release(bike: string): void {
    if (!this.docked.has(bike)) throw new Error(`${bike} is not at this dock`);
    this.docked.delete(bike);
  }
  lock(bike: string): void {
    if (this.docked.has(bike)) throw new Error(`${bike} is already docked`);
    this.docked.add(bike);
  }
}

// Not written yet: every rider gets the phone's vibration, even with haptics off.
function chooseHaptics(hapticsOn: boolean): Haptics {
  return new PhoneHaptics();
}

class RideFlow {
  private dock: MemoryDock;
  private phone: PhoneHaptics;
  private hapticsOn: boolean;
  constructor(dock: MemoryDock, phone: PhoneHaptics, hapticsOn: boolean) {
    this.dock = dock;
    this.phone = phone;
    this.hapticsOn = hapticsOn;
  }

  unlock(bike: string): string {
    if (bike === '') return 'failed: no bike id';
    try {
      this.dock.release(bike);
    } catch (error) {
      return `failed: ${(error as Error).message}`;
    }
    if (this.hapticsOn) this.phone.pulse('unlocked');
    return `unlocked ${bike}`;
  }

  // Added later, for the end of a ride.
  endRide(bike: string): string {
    try {
      this.dock.lock(bike);
    } catch (error) {
      return `failed: ${(error as Error).message}`;
    }
    this.phone.pulse('locked');
    return `locked ${bike}`;
  }
}

// The app's boundary: where the rider's setting reaches the flow.
function startRideFlow(dock: MemoryDock, hapticsOn: boolean): RideFlow {
  return new RideFlow(dock, new PhoneHaptics(), hapticsOn);
}

// The platform team's dock-arrival nudge: it pulses whatever Haptics the
// app hands it, without asking.
function nearbyNudge(haptics: Haptics): string {
  haptics.pulse('nearby');
  return 'nudged';
}