四-State: changing object behavior

4-State: changing object behavior

An object that appears to change its class.

Indications: conditional code in most or all methods.

The State pattern switches from one implementation to another during the lifetime of the surrogate, in order to produce different behavior from the same method call(s). It’s a way to improve the implementation of your code when you seem to be doing a lot of testing inside each of your methods before deciding what to do for that method. For example, the fairy tale of the frog-prince contains an object (the creature) that behaves differently depending on what state it’s in. You could implement this using a boolean that you test:

//: state:KissingPrincess.java

package state;

import junit.framework.*;

 

class Creature {

  private boolean isFrog = true;

  public void greet() {

    if(isFrog)

      System.out.println("Ribbet!");

    else

      System.out.println("Darling!");

  }

  public void kiss() { isFrog = false; }

}

 

public class KissingPrincess extends TestCase  {

  Creature creature = new Creature();

  public void test() {

    creature.greet();

    creature.kiss();

    creature.greet();

  }

  public static void main(String args[]) {

    junit.textui.TestRunner.run(KissingPrincess.class);

  }

} ///:~

 

However, the greet() method, and any other methods that must test isFrog before they perform their operations, ends up with awkward code. By delegating the operations to a State object that can be changed, this code is simplified.

//: state:KissingPrincess2.java

package state;

import junit.framework.*;

 

class Creature {

  private interface State {

    String response();

  }

  private class Frog implements State {

    public String response() { return "Ribbet!"; }

  }

  private class Prince implements State {

    public String response() { return "Darling!"; }

  }

  private State state = new Frog();

  public void greet() {

    System.out.println(state.response());

  }

  public void kiss() { state = new Prince(); }

}

 

public class KissingPrincess2 extends TestCase  {

  Creature creature = new Creature();

  public void test() {

    creature.greet();

    creature.kiss();

    creature.greet();

  }

  public static void main(String args[]) {

    junit.textui.TestRunner.run(KissingPrincess2.class);

  }

} ///:~

 

In addition, changes to the State are automatically propagated throughout, rather than requiring an edit across the class methods in order to effect changes.

Here’s the basic structure of State:

 

//: state:StateDemo.java

// Simple demonstration of the State pattern.

package state;

import junit.framework.*;

 

interface State {

  void operation1();

  void operation2();

  void operation3();

}

 

class ServiceProvider {

  private State state;

  public ServiceProvider(State state) {

    this.state = state;

  }

  public void changeState(State newState) {

    state = newState;

  }

  // Pass method calls to the implementation:

  public void service1() {

    // ...

    state.operation1();

    // ...

    state.operation3();

  }

  public void service2() {

    // ...

    state.operation1();

    // ...

    state.operation2();

  }

  public void service3() {

    // ...

    state.operation3();

    // ...

    state.operation2();

  }

}

 

class Implementation1 implements State {

  public void operation1() {

    System.out.println("Implementation1.operation1()");

  }

  public void operation2() {

    System.out.println("Implementation1.operation2()");

  }

  public void operation3() {

    System.out.println("Implementation1.operation3()");

  }

}

 

class Implementation2 implements State {

  public void operation1() {

    System.out.println("Implementation2.operation1()");

  }

  public void operation2() {

    System.out.println("Implementation2.operation2()");

  }

  public void operation3() {

    System.out.println("Implementation2.operation3()");

  }

}

 

public class StateDemo extends TestCase  {

  static void run(ServiceProvider sp) {

    sp.service1();

    sp.service2();

    sp.service3();

  }

  ServiceProvider sp =

    new ServiceProvider(new Implementation1());

  public void test() {

    run(sp);

    sp.changeState(new Implementation2());

    run(sp);

  }

  public static void main(String args[]) {

    junit.textui.TestRunner.run(StateDemo.class);

  }

} ///:~

 

Proxy 和 State 总结:

Object decoupling

Both Proxy and State provide a surrogate class that you use in your code; the real class that does the work is hidden behind this surrogate class. When you call a method in the surrogate, it simply turns around and calls the method in the implementing class. These two patterns are so similar that the Proxy is simply a special case of State . One is tempted to just lump the two together into a pattern called Surrogate , but the term “proxy” has a long-standing and specialized meaning, which probably explains the reason for the two different patterns.

The basic idea is simple: from a base class, the surrogate is derived along with the class or classes that provide the actual implementation:


                      四-State: changing object behavior

When a surrogate object is created, it is given an implementation to which to send all of the method calls.

Structurally, the difference between Proxy and State is simple: a Proxy has only one implementation, while State has more than one. The application of the patterns is considered (in Design Patterns ) to be distinct: Proxy is used to control access to its implementation, while State allows you to change the implementation dynamically. However, if you expand your notion of “controlling access to implementation” then the two fit neatly together.