Skip to main content

  | मौसम…

411 समाचार
Public archive edition · Attributed source reporting with original dates · AI drafts are excluded

411 REFERENCE · Wikibooks

Computer Science Design Patterns/State

Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.

reading 25 31 December 2014 Add more illustrations. Singleton Strategy Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class. #include <iostream> using namespace std; class Machine { class State *current; public: Machine(); void setCurrent(State *s) { current = s; } void on(); void off(); }; class State { public: virtual void on(Machine *m) { cout << " already ON\n"; } virtual void off(Machine *m) { cout << " already OFF\n"; } }; void Machine::on() { current->on(this); } void Machine::off() { current->off(this); } class ON: public State { public: ON() { cout << " ON-ctor "; }; ~ON() { cout << " dtor-ON\n"; }; void off(Machine *m); }; class OFF: public State { public: OFF() { cout << " OFF-ctor "; }; ~OFF() { cout << " dtor-OFF\n"; }; void on(Machine *m) { cout << " going from OFF to ON"; m->setCurrent(new ON()); delete this; } }; void ON::off(Machine *m) { cout << " going from ON to OFF"; m->setCurrent(new OFF()); delete this; } Machine::Machine() { current = new OFF(); cout << '\n'; } int main() { void(Machine:: *ptrs[])() = { Machine::off, Machine::on }; Machine fsm; int num; while (1) { cout << "Enter 0/1: "; cin >> num; (fsm. *ptrs[num])(); } } 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 ++count; 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 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()revised from http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/ 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 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 Hack 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 IState { /** * Writer method for the state name. */ public function write(StateContext $context, string $name): void; } use namespace HH\Lib\Str; final class LowerCaseState implements IState { public function write(StateContext $context, string $name): void { print Str\lowercase($name) . "\n"; $context->setState(new MultipleUpperCaseState()); } } use namespace HH\Lib\Str; final class MultipleUpperCaseState implements IState { private int $count = 0; public function write(StateContext $context, string $name): void { print Str\uppercase($name) . "\n"; ++$this->count; if ($this->count > 1) { $context->setState(new LowerCaseState()); } } } 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. final class StateContext { public function __construct( private IState $state = new LowerCaseState() ) {} /** * Set the current state. * Normally only called by classes implementing the State interface. */ public function setState(IState $state): void { $this->state = $state; } public function write(string $name): void { $this->state->write($this, $name); } } The test below shows also the usage: require 'vendor/hh_autoload.hh'; <<__EntryPoint>> async function main(): Awaitable<void> { $context = new StateContext(); $context->write('Monday'); $context->write('Tuesday'); $context->write('Wednesday'); $context->write('Thursday'); $context->write('Friday'); $context->write('Saturday'); $context->write('Sunday'); } According to the above code, the output of main() entry point should be: monday TUESDAY WEDNESDAY thursday FRIDAY SATURDAY sunday ==References==