package Ticket; use strict; use warnings; ... sub state { my $self = shift; $self->{state} ||= TicketState::New->new(); } sub set_state { my $self = shift; my $new_state = shift; push @{$self->{statelog}}, $self->{state}; $self->{state} = $new_state; return $self; } sub send_to_supervisor { my $self = shift; $self->state->send_ticket_to_supervisor(@_); } sub approve { my $self = shift; $self->state->approve_ticket($self, @_); } sub reject { my $self = shift; $self->state->reject_ticket($self, @_); } package TicketState::New; ... sub send_ticket_to_supervisor { my $self = shift; my $ticket = shift; my $supervisor = $SUPERVISORS{$ticket->topic}; # Deliberately naive $ticket->set_state(TicketState::PendingApproval->new); $supervisor->accept($ticket); return $ticket; } sub approve_ticket { die "You cannot approve a ticket in this state" } sub reject_ticket { die "You cannot reject a ticket in this state" } package TicketState::PendingApproval; sub send_ticket_to_supervisor { die "Ticket was already sent to a supervisor!"; } sub approve_ticket { my $self = shift; my($ticket, $supervisor) = @_; $ticket->set_state(TicketState::Approved->new ->set_owner($supervisor)); $supervisor->editorial_group->accept($ticket); return $ticket; } sub reject_ticket { my $self = shift; my($ticket, $supervisor) = @_; $ticket->set_state(TicketState::Rejected->new ->set_owner($supervisor)); $ticket->author->accept($ticket); return $ticket; } package TicketState::Rejected; # Almost indistinguishable from TicketState::New use base 'TicketState::New';