From harness-claude
Replaces conditional logic with state objects that delegate behavior to the current state. Useful for state machines like order lifecycle, connection states, or traffic lights.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:gof-state-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Replace conditional logic with state objects that delegate behavior to the current state.
Replace conditional logic with state objects that delegate behavior to the current state.
if/else or switch chains that check state and choose behaviorState interface + concrete states:
// State interface
interface OrderState {
pay(order: Order): void;
ship(order: Order): void;
cancel(order: Order): void;
getLabel(): string;
}
// Context — delegates behavior to current state
class Order {
private state: OrderState;
constructor(public readonly id: string) {
this.state = new PendingState();
}
// Delegate all behavior to the current state
pay(): void {
this.state.pay(this);
}
ship(): void {
this.state.ship(this);
}
cancel(): void {
this.state.cancel(this);
}
getLabel(): string {
return this.state.getLabel();
}
// State transitions are initiated by state objects
setState(state: OrderState): void {
console.log(`Order ${this.id}: ${this.state.getLabel()} → ${state.getLabel()}`);
this.state = state;
}
}
// Concrete states
class PendingState implements OrderState {
pay(order: Order): void {
console.log(`Processing payment for order ${order.id}`);
order.setState(new PaidState());
}
ship(order: Order): void {
throw new Error('Cannot ship: order not paid');
}
cancel(order: Order): void {
console.log(`Cancelling order ${order.id}`);
order.setState(new CancelledState());
}
getLabel(): string {
return 'Pending';
}
}
class PaidState implements OrderState {
pay(order: Order): void {
throw new Error('Order is already paid');
}
ship(order: Order): void {
console.log(`Shipping order ${order.id}`);
order.setState(new ShippedState());
}
cancel(order: Order): void {
console.log(`Refunding payment for order ${order.id}`);
order.setState(new CancelledState());
}
getLabel(): string {
return 'Paid';
}
}
class ShippedState implements OrderState {
pay(order: Order): void {
throw new Error('Already paid and shipped');
}
ship(order: Order): void {
throw new Error('Already shipped');
}
cancel(order: Order): void {
throw new Error('Cannot cancel: already shipped');
}
getLabel(): string {
return 'Shipped';
}
}
class CancelledState implements OrderState {
pay(order: Order): void {
throw new Error('Order is cancelled');
}
ship(order: Order): void {
throw new Error('Order is cancelled');
}
cancel(order: Order): void {
throw new Error('Already cancelled');
}
getLabel(): string {
return 'Cancelled';
}
}
// Usage
const order = new Order('ORD-001');
order.pay(); // Pending → Paid
order.ship(); // Paid → Shipped
Discriminated union state machine (TypeScript-idiomatic):
type ConnectionState =
| { status: 'disconnected' }
| { status: 'connecting'; attempt: number }
| { status: 'connected'; sessionId: string; connectedAt: Date }
| { status: 'error'; reason: string; lastAttempt: Date };
class Connection {
private state: ConnectionState = { status: 'disconnected' };
async connect(): Promise<void> {
if (this.state.status === 'connected') return;
this.state = { status: 'connecting', attempt: 1 };
try {
const sessionId = await this.doConnect();
this.state = { status: 'connected', sessionId, connectedAt: new Date() };
} catch (err) {
this.state = { status: 'error', reason: (err as Error).message, lastAttempt: new Date() };
}
}
getState(): ConnectionState {
return this.state;
}
// TypeScript narrows the type in each branch
getSessionId(): string {
if (this.state.status !== 'connected') throw new Error('Not connected');
return this.state.sessionId; // TypeScript knows this exists
}
private async doConnect(): Promise<string> {
return 'session-' + Math.random();
}
}
State vs. Strategy: Both replace conditionals with polymorphism. State: the state object controls transitions and the context changes its own state. Strategy: the strategy is set externally and doesn't change itself. If the object decides when to change behavior, use State. If an external caller decides, use Strategy.
Where to put transition logic: States can transition the context directly (order.setState(new PaidState())) or return the new state for the context to apply. Prefer direct transitions when states are simple; prefer returning state when you want to prevent circular dependencies.
Anti-patterns:
Persistence: When serializing an object with state, store the state label ('Pending', 'Paid') and restore the state object on load:
function restoreState(label: string): OrderState {
switch (label) {
case 'Pending':
return new PendingState();
case 'Paid':
return new PaidState();
case 'Shipped':
return new ShippedState();
case 'Cancelled':
return new CancelledState();
default:
throw new Error(`Unknown state: ${label}`);
}
}
refactoring.guru/design-patterns/state
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeUse when an object's behavior changes based on its internal state and the logic for each state is complex — replacing large conditional chains with state objects where each state encapsulates its own behavior.
Implements the State pattern in JavaScript to encapsulate state-dependent behavior into separate classes, eliminating complex conditionals.
Generates State pattern for PHP 8.4 including context, state interface, concrete states, factory, entity updates, exceptions, and unit tests for state-dependent object behavior like order workflows.