Key Principle
The UCM architecture separates user interface software into three layers -- UI objects, Control objects, and Model objects -- with the Control layer serving as the explicit behavioral coordination layer that is absent in bottom-up construction. Control objects maintain state variables, receive delegated events from thin UI handlers, determine actions based on event-plus-current-state, and mediate all communication between UI and Model. Statecharts provide the design notation that specifies what control objects do; coding those statecharts into control objects is then mechanical.
Why This Matters
Bottom-up code distributes coordination logic across event handlers communicating through shared mutable state. The calculator's DecimalFlag variable, accessed by six of seven handlers, exemplifies this: coupling spans nearly the entire application and behavior becomes impossible to reason about as a whole. UCM solves this by centralizing all behavioral coordination in control objects, making state explicit and localized. Without this separation, statecharts have nowhere to live in code -- the architecture and the notation are complementary halves of the same solution.
Good Examples
- Thin event handlers: Event handlers contain only a delegation call to the control object. "No other code is contained in the event handlers." (Ch. 13). This enforces the statechart as the single source of truth for behavior. If event handlers contain logic or if state variables are publicly accessible, code can bypass the statechart's transition rules -- reintroducing ad-hoc state management.
- State variable per hierarchy level: Each decomposition level in the statechart maps to one state variable in the control object; each concurrent region gets its own variable. This mechanical mapping prevents the system from losing track of its actual configuration.
- State procedures without conditionals: Each state is coded as a procedure (
go_state_N) that always sets the same UI attributes to the same values. If a conditional appears inside a state procedure, the statechart is missing states -- the design is incomplete, not the code. This rule turns the coding phase into a design-verification step. - Cascading entry calls: Entering a parent state with substates requires calling state procedures at every hierarchy level. Skipping a level leaves child state variables stale -- a subtle bug source that disciplined mapping prevents.
- Transient states as routing nodes: Transient states update the state variable and evaluate conditions but never modify UI attributes, keeping conditional branching visible in the statechart rather than buried in code.
- Modal dialogs as states: A modal alert (Yes/No/Cancel) is modeled as its own state with transitions for each button response, preventing the common pattern of scattering save/discard/cancel logic across callbacks.
- Prioritized events resolving non-determinism: When multiple transitions leave the same state on the same event with different guard conditions, priority numbers on the statechart resolve ambiguity at design time. The code's if/elsif ordering directly implements that resolution.
Counterpoints
- UCM alone is insufficient. Without a notation powerful enough to specify control-object behavior, the control layer risks becoming the same tangle of conditionals it was meant to replace -- "just relocated into a different layer." The architecture requires statecharts as its complement.
- The historical objection that state-based UI design produces unmanageable state counts is "a result of using state transition diagrams as the design notation" (Ch. 4), not a flaw in the paradigm itself. Statecharts resolve this through hierarchy and concurrency.
- Control objects handle only application-specific coordination logic. Built-in widget behaviors (tab ordering, focus management) remain in the UI framework's property configuration to prevent the control layer from bloating.
Key Quotes
- "The code that makes the objects work together is distributed throughout the event handlers." (Ch. 4) -- the shared-state anti-pattern that motivates UCM.
- "It is like constructing a building without solid foundations." (Ch. 4) -- on building UIs without an explicit architectural layer for behavior.
- "This is not a theory of how user interface software could be constructed. It is the nature of all direct manipulation user interfaces." (Ch. 4) -- the event-state-action paradigm as discovery, not prescription.
- "A user can only supply valid events at any given time." (Ch. 4) -- the developer's core responsibility in direct manipulation UIs.
- "A control object controls and co-ordinates the behaviour of a set of user interface objects." (Ch. 13) -- the definition of the control layer's role.
- "No other code is contained in the event handlers." (Ch. 13) -- the thin-handler discipline.
- "State procedures always set the same attributes to the same values. In other words there should be no conditional statements which could cause different attributes to be set." (Ch. 13) -- the absolute no-conditionals rule for state procedures.
- "Coding a statechart is a simple process that can be completed very quickly once the design has been finalized." (Ch. 13) -- mechanical coding as validation of design-before-code.
- "If the same event can occur in two different contexts and the resulting actions are identical, it is still strongly advised that the code for the two events are kept separate." (Ch. 13) -- context isolation over code reuse.
- "The advantage of using a statechart to control a user interface is that distinct contexts in which events can occur are easily identifiable." (Ch. 13) -- statecharts making debugging instrumentation natural.
Rules of Thumb
- One state variable per hierarchy level, one per concurrent region. If you cannot point to the variable for a given level, the mapping is incomplete.
- No conditionals in state procedures. A conditional inside
go_state_Nmeans the statechart has undiscovered states. Fix the design, not the code. - Event handlers are pure delegation. Any logic in an event handler beyond calling the control object is a statechart bypass and reintroduces ad-hoc state management.
- UI objects never access Model objects directly; Model objects never message UI objects. The control layer mediates all communication between the two.
- Context isolation over code reuse. Even if two contexts produce identical behavior, keep their code separate -- during maintenance, changing one must not silently break the other.
- Use explicit history variables even when technically unnecessary. Preservation of state should be a visible design choice, not an accident of implementation.
- Architecture without notation is reorganization, not solution. UCM needs statecharts; statecharts need UCM. Neither alone suffices.
Related References
- Statechart hierarchy and concurrency notation (design chapters)
- Bottom-up construction anti-patterns (Ch. 3) -- the motivation for UCM
- Event-action tables as the interface contract between control objects
- Concurrency modeling and the swap test for independence verification (Ch. 13)
- History mechanism and explicit state preservation (Ch. 13)
- Jacobson et al. (1993) -- stable-system partition into presentation, behavior, information aligns with UCM's three layers