跳轉到內容

狀態

25% developed
來自華夏公益教科書

單例模式 計算機科學設計模式
狀態
策略模式

允許物件在內部狀態改變時改變其行為。物件將看起來改變了它的類。

#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])();
  }
}
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);
    }
  }
Java 實現

狀態介面和兩個實現。狀態的方法引用了上下文物件,並能夠改變其狀態。

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());
        }
    }
}

上下文類有一個狀態變數,它在一個初始狀態例項化,在本例中是 StateA。在它的方法中,它使用狀態物件的對應方法。

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);
    }
}

下面的測試也顯示了用法

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");
    }
}

根據上面的程式碼,TestClientState 的 main() 的輸出應該是

monday
TUESDAY
WEDNESDAY
thursday
FRIDAY
SATURDAY
sunday
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();
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]

根據上面的 Perl 和 Python 程式碼,main() 的輸出應該是

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
Ruby 實現

在下面的 Ruby 示例中,收音機可以切換到兩個狀態 AM 和 FM,並有一個掃描按鈕切換到下一個電臺。

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
Hack 實現

狀態介面和兩個實現。狀態的方法引用了上下文物件,並能夠改變其狀態。

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());
    }
  }
}

上下文類有一個狀態變數,它在一個初始狀態例項化,在本例中是 StateA。在它的方法中,它使用狀態物件的對應方法。

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);
  }
}

下面的測試也顯示了用法

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');
}

根據上面的程式碼,main() 入口點的輸出應該是

monday
TUESDAY
WEDNESDAY
thursday
FRIDAY
SATURDAY
sunday

參考資料


Clipboard

待辦事項
新增更多插圖。


單例模式 計算機科學設計模式
狀態
策略模式


您對本頁有任何疑問嗎?
在此提問


在本圖書上建立一個新頁面


華夏公益教科書