From harness-claude
Implements GOF State Pattern to replace if/else chains with state objects delegating behavior by current state. For state machines like order lifecycles, connections, vending machines.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Replace conditional logic with state objects that delegate behavior to the current state.
Implements JavaScript State pattern to encapsulate state-dependent behavior and transitions. Replaces complex conditionals for workflows, UI state machines, or protocol handlers with 3+ states.
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.
Designs finite state machines and statecharts for modeling entity lifecycles, workflows, and system behaviors using Harel semantics, PlantUML, and Mermaid notation.
Share bugs, ideas, or general feedback.
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