Memra

Architecture: Event / Controller / GreenhouseControls

Walk the control-framework pattern end to end — an abstract Event, a reusable Controller that runs an event list, and a concrete GreenhouseControls that supplies the events as inner classes. This is the design question the exam asks.

The whole project in three layers

The Greenhouse capstone is not a pile of features — it is one control framework applied to one domain. Three classes carry the entire design, and the exam's design/trace question is almost always *"explain how these three fit together."*

  1. Event (abstract) — the *what-happens-and-when* contract. It knows a delay, computes a target time, can report whether it is ready(), and declares an abstract action(). It does not know *what* the action is — that is the subclass's job. This is the Template Method shape: the base fixes the timing machinery, the subclass fills the one varying step.
  2. Controller (reusable) — the *engine*. It holds a List<Event>, exposes addEvent, and run()s a loop that repeatedly scans the list, fires every event whose time has come, and removes it. The controller is domain-agnostic: it would run a sprinkler system or a rocket sequencer unchanged, because it only ever talks to the Event abstraction.
  3. GreenhouseControls extends Controller — the *application*. It adds the greenhouse state (light, water, thermostat) and defines each concrete event — LightOn, WaterOff, Bell, Terminate… — as inner classes of itself. Because they are inner classes, each action() can read and write the enclosing greenhouse's state directly.

Why an abstract Event

Look at the base contract:

public abstract class Event {
    private long eventTime;
    protected final long delayTime;
    public Event(long delayTime) {
        this.delayTime = delayTime;
        start();
    }
    public void start() {                       // allows restarting
        eventTime = System.currentTimeMillis() + delayTime;
    }
    public boolean ready() {
        return System.currentTimeMillis() >= eventTime;
    }
    public abstract void action();
}

The constructor stores the relative delayTime and immediately calls start(), which converts it to an absolute eventTime (now + delay). ready() is a pure time check. action() is abstract, so Event cannot be instantiated — you *must* subclass it and say what the action does. That single abstract method is the seam the whole framework pivots on.

Why a generic Controller

public class Controller {
    private List<Event> eventList = new ArrayList<Event>();
    public void addEvent(Event c) { eventList.add(c); }
    public void run() {
        while (eventList.size() > 0)
            for (Event e : new ArrayList<Event>(eventList))
                if (e.ready()) {
                    System.out.println(e);
                    e.action();
                    eventList.remove(e);
                }
    }
}

The controller declares its field to the interface List, not the implementation ArrayList (Module 5) — so the storage choice can change without touching run(). run() keeps looping while any events remain. Each pass it iterates a defensive copy (new ArrayList<Event>(eventList)) so it can safely eventList.remove(e) from the *original* list while iterating — modifying the live list mid-iteration would throw ConcurrentModificationException. For every ready event it prints the event (its toString()), runs action(), and removes it. Polymorphism does the dispatch: e.action() calls whichever subclass's override the actual object has.

Why inner classes for the events

public class GreenhouseControls extends Controller {
    private boolean light = false;
    private boolean water = false;
    private String thermostat = "Day";

    public class LightOn extends Event {
        public LightOn(long delayTime) { super(delayTime); }
        public void action() { light = true; }      // touches OUTER state
        public String toString() { return "Light is on"; }
    }
    // ...LightOff, WaterOn, ThermostatNight, Bell, Terminate, Restart...
}

LightOn.action() writes light = true — and light is a field of the enclosing GreenhouseControls. An inner class holds an implicit reference to its outer instance, so it reaches that state with no parameters and no getters. That is exactly why the project uses inner classes rather than top-level classes: each event *is* a behaviour bound to one greenhouse's state.

The flow, start to finish

main parses -f <file>, creates a GreenhouseControls, seeds it with a Restart event that loads the initial schedule, and calls run(). The controller then drives everything: time advances, events become ready(), their action()s mutate greenhouse state and print a status line, and the loop ends when Terminate.action() (which calls System.exit(0)) stops the JVM.

NORMAL ~/memra/learn/comp-308/architecture-walkthrough utf-8 LF