State

Observer Computer Science Design Patterns
State
Strategy

Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class.

Implementation in C#
using System;
 
  class MainApp
  {
    static void Main()
    {
      // Setup context in a state
      Context c = new Context(new ConcreteStateA());
 
      // Issue requests, which toggles state
      c.Request();
      c.Request();
      c.Request();
      c.Request();
 
      // Wait for user
      Console.Read();
    }
  }
 
  // "State"
  abstract class State
  {
    public abstract void Handle(Context context);
  }
 
  // "ConcreteStateA"
  class ConcreteStateA : State
  {
    public override void Handle(Context context)
    {
      context.State = new ConcreteStateB();
    }
  }
 
  // "ConcreteStateB"
  class ConcreteStateB : State
  {
    public override void Handle(Context context)
    {
      context.State = new ConcreteStateA();
    }
  }
 
  // "Context"
  class Context
  {
    private State state;
 
    // Constructor
    public Context(State state)
    {
      this.State = state;
    }
 
    // Property
    public State State
    {
      get{ return state; }
      set
      {
        state = value;
        Console.WriteLine("State: " +
          state.GetType().Name);
      }
    }
 
    public void Request()
    {
      state.Handle(this);
    }
  }
Implementation in Java

The state interface and two implementations. The state's method has a reference to the context object and is able to change its state.

interface Statelike {
 
    /**
     * Writer method for the state name.
     * @param STATE_CONTEXT
     * @param NAME
     */
    void writeName(final StateContext STATE_CONTEXT, final String NAME);
 
}
class StateA implements Statelike {
    /* (non-Javadoc)
     * @see state.Statelike#writeName(state.StateContext, java.lang.String)
     */
    @Override
    public void writeName(final StateContext STATE_CONTEXT, final String NAME) {
        System.out.println(NAME.toLowerCase());
        STATE_CONTEXT.setState(new StateB());
    }
 
}
class StateB implements Statelike {
    /** State counter */
    private int count = 0;
 
    /* (non-Javadoc)
     * @see state.Statelike#writeName(state.StateContext, java.lang.String)
     */
    @Override
    public void writeName(final StateContext STATE_CONTEXT, final String NAME) {
        System.out.println(NAME.toUpperCase());
        // Change state after StateB's writeName() gets invoked twice
        if(++count > 1) {
            STATE_CONTEXT.setState(new StateA());
        }
    }
 
}

The context class has a state variable that it instantiates in an initial state, in this case StateA. In its method, it uses the corresponding methods of the state object.

public class StateContext {
    private Statelike myState;
        /**
         * Standard constructor
         */
    public StateContext() {
        setState(new StateA());
    }
 
        /**
         * Setter method for the state.
         * Normally only called by classes implementing the State interface.
         * @param NEW_STATE
         */
    public void setState(final Statelike NEW_STATE) {
        myState = NEW_STATE;
    }
 
        /**
         * Writer method
         * @param NAME
         */
    public void writeName(final String NAME) {
        myState.writeName(this, NAME);
    }
}

The test below shows also the usage:

public class TestClientState {
    public static void main(String[] args) {
        final StateContext SC = new StateContext();
 
        SC.writeName("Monday");
        SC.writeName("Tuesday");
        SC.writeName("Wednesday");
        SC.writeName("Thursday");
        SC.writeName("Friday");
        SC.writeName("Saturday");
        SC.writeName("Sunday");
    }
}

According to the above code, the output of main() from TestClientState should be:

monday
TUESDAY
WEDNESDAY
thursday
FRIDAY
SATURDAY
sunday
Implementation in Perl
use strict;
use warnings;
 
package State;
# base state, shared functionality
use base qw{Class::Accessor};
# scan the dial to the next station
sub scan {
	my $self = shift;
	printf "Scanning... Station is %s %s\n",
		$self->{stations}[$self->{pos}], $self->{name};
	$self->{pos}++;
	if ($self->{pos} >= @{$self->{stations}}) {
		$self->{pos} = 0
	}
}
 
package AmState;
our @ISA = qw(State);
AmState->mk_accessors(qw(radio pos name));
use Scalar::Util 'weaken';
sub new {
	my ($class, $radio) = @_;
	my $self;
	@$self{qw(stations pos name radio)} = 
		([1250,1380,1510], 0, 'AM', $radio);
	# make circular reference weak
	weaken $self->{radio};
	bless $self, $class;
}
sub toggle_amfm {
	my $self = shift;
	print "Switching to FM\n";
	$self->radio->state( $self->radio->fmstate );
}
 
package FmState;
our @ISA = qw(State);
FmState->mk_accessors(qw(radio pos name));
use Scalar::Util 'weaken';
sub new {
	my ($class, $radio) = @_;
	my $self;
	@$self{qw(stations pos name radio)} = 
		([81.3,89.3,103.9], 0, 'FM', $radio);
	# make circular reference weak
	weaken $self->{radio};
	bless $self, $class;
}
sub toggle_amfm {
	my $self = shift;
	print "Switching to AM\n";
	$self->radio->state( $self->radio->amstate );
}
 
package Radio;
# this is a radio, it has a scan button and a am/fm toggle
use base qw{Class::Accessor};
Radio->mk_accessors(qw(amstate fmstate state));
sub new {
	my $class = shift;
	my $self  = {};
	bless $self, $class;
 
	@$self{ 'amstate', 'fmstate' }
		= ( AmState->new($self), FmState->new($self), );
	$self->state( $self->amstate );
	$self;
}
sub toggle_amfm {
	shift->state->toggle_amfm;
}
sub scan {
	shift->state->scan;
}
 
package main;
# test out our radio
sub main {
	my $radio = Radio->new;
	my @actions = (
		('scan')x2,
		('toggle_amfm'),
		('scan')x2
	)x2;
 
	for my $action (@actions) {
		$radio->$action;
	}
	exit;
 
}
 
main();
Implementation in Ruby

In the Ruby example below, a radio can switch to two states AM and FM and has a scan button to switch to the next station.

class State
    def scan
        @pos += 1
        @pos = 0 if @pos == @stations.size
        puts "Scanning… Station is", @stations[@pos], @name
    end
end
 
class AmState < State
    attr_accessor :radio, :pos, :name
    def initialize radio
        @radio = radio
        @stations = ["1250", "1380", "1510"]
        @pos = 0
        @name = "AM"
    end
 
    def toggle_amfm
        puts "Switching to FM"
        @radio.state = @radio.fmstate
    end
end
 
class FmState < State
    attr_accessor :radio, :pos, :name
    def initialize radio
        @radio = radio
        @stations = ["81.3", "89.1", "103.9"]
        @pos = 0
        @name = "FM"
    end
 
    def toggle_amfm
        puts "Switching to AM"
        @radio.state = @radio.amstate
    end
end
 
class Radio
    attr_accessor :amstate, :fmstate, :state
    def initialize
        @amstate = AmState.new self
        @fmstate = FmState.new self
        @state = @amstate
    end
 
    def toggle_amfm
        @state.toggle_amfm
    end
 
    def scan
        @state.scan
    end
end
 
# Test Radio
radio = Radio.new
radio.scan 
radio.toggle_amfm # Toggle the state
radio.scan
Implementation in Python
import itertools
 
"""Implementation of the state pattern"""
class State(object):
    """Base state. This is to share functionality"""
 
    def scan(self):
        """Scan the dial to the next station"""
        print "Scanning... Station is", self.stations.next(), self.name
 
class AmState(State):
    def __init__(self, radio):
        self.radio = radio
        self.stations = itertools.cycle(["1250", "1380", "1510"])
        self.name = "AM"
 
    def toggle_amfm(self):
        print "Switching to FM"
        self.radio.state = self.radio.fmstate
 
class FmState(State):
    def __init__(self, radio):
        self.radio = radio
        self.stations = itertools.cycle(["81.3", "89.1", "103.9"])
        self.name = "FM"
 
    def toggle_amfm(self):
        print "Switching to AM"
        self.radio.state = self.radio.amstate
 
class Radio(object):
    """A radio.
    It has a scan button, and an AM/FM toggle switch."""
 
    def __init__(self):
        """We have an AM state and an FM state"""
 
        self.amstate = AmState(self)
        self.fmstate = FmState(self)
        self.state = self.amstate
 
    def toggle_amfm(self):
        self.state.toggle_amfm()
 
    def scan(self):
        self.state.scan()
 
def main():
    ''' Test our radio out '''
    radio = Radio()
    actions = ([radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2) * 2
    for action in actions:
        action()
 
if __name__ == '__main__':
    main()
[1]

According to the above Perl and Python code, the output of main() should be:

Scanning... Station is 1250 AM
Scanning... Station is 1380 AM
Switching to FM
Scanning... Station is 81.3 FM
Scanning... Station is 89.1 FM
Scanning... Station is 103.9 FM
Scanning... Station is 81.3 FM
Switching to AM
Scanning... Station is 1510 AM
Scanning... Station is 1250 AM

References


Clipboard

To do:
Add more illustrations.


Observer Computer Science Design Patterns
State
Strategy


You have questions about this page?
Ask it here:


Create a new page on this book:

Last modified on 22 May 2013, at 19:21