Sponsored Link •
|
Advertisement
|
Although most
This StampDispenser
really offers the same service to clients as the
StampDispenser
in example 1, but through
a different interface and with an implementation that is more code-heavy than
example 1. Were I to have to implement a StampDispenser
, I would
do it the way I did in example 1. With example 2, I wanted to show how state
machines and service-oriented objects are similar, and I wanted to have a launching
point for using the state pattern.
The state pattern is an implementation pattern, so it doesn't really impact APIs, but it will allow me to show you a good example of a flyweight. The state pattern says that when you end up with switch statements such as those that appear in example 2, where you have a case statement for each of some finite number of states, you should consider factoring out the behavior for each state into its own object. You end up with an implementation that looks like this:
You then define one concrete subclass for each state:
1 package com.artima.examples.stampdispenser.ex3; 2 3 import java.util.Set; 4 5 /** 6 * <code>StampDispenserState</code> subclass that represents 7 * the <em>Has0</em> stamp dispenser state. See the documentation 8 * for class <code>StampDispenserState</code> for a state 9 * transition table that specifies the required behavior of 10 * instances of this class. 11 */ 12 class Has0State extends StampDispenserState { 13 14 /** 15 * The single instance of <code>Has0State</code> 16 */ 17 private static Has0State singleton = new Has0State(); 18 19 /** 20 * Constructs a <code>Has0State</code> instance. This constructor is 21 * private to enable this class to restrict the number 22 * of instances of <code>Has0State</code> to one. I.e., <code>Has0State</code> is 23 * a singleton. 24 */ 25 private Has0State() { 26 } 27 28 /** 29 * Factory method that returns the single instance of 30 * <code>Has0State</code>. 31 */ 32 static StampDispenserState getState() { 33 return singleton; 34 } 35 36 /** 37 * Performs actions appropriate for the state represented by 38 * the class of this object for the add5 message. Returns a 39 * reference to the state object that represents the next state 40 * to transition to as a result of the arrival of this add5 message. 41 * 42 * @param stampDispenser the <code>StampDispenser</code> whose state this 43 * object represents 44 * @param listeners <code>Set</code> containing zero to many (and only) 45 * <code>StampDispenserListener</code>s 46 */ 47 StampDispenserState add5(StampDispenser stampDispenser, 48 Set listeners) { 49 50 StampDispenserEvent event = new StampDispenserEvent(stampDispenser, 0, 5); 51 fireCoinAccepted(event, listeners); 52 return Has5State.getState(); 53 } 54 55 /** 56 * Performs actions appropriate for the state represented by 57 * the class of this object for the add10 message. Returns a 58 * reference to the state object that represents the next state 59 * to transition to as a result of the arrival of this add10 message. 60 * 61 * @param stampDispenser the <code>StampDispenser</code> whose state this 62 * object represents 63 * @param listeners <code>Set</code> containing zero to many (and only) 64 * <code>StampDispenserListener</code>s 65 */ 66 StampDispenserState add10(StampDispenser stampDispenser, 67 Set listeners) { 68 69 StampDispenserEvent event = new StampDispenserEvent(stampDispenser, 0, 10); 70 fireCoinAccepted(event, listeners); 71 return Has10State.getState(); 72 } 73 74 /** 75 * Performs actions appropriate for the state represented by 76 * the class of this object for the returnCoins message. Returns a 77 * reference to the state object that represents the next state 78 * to transition to as a result of the arrival of this returnCoins message. 79 * 80 * @param stampDispenser the <code>StampDispenser</code> whose state this 81 * object represents 82 * @param listeners <code>Set</code> containing zero to many (and only) 83 * <code>StampDispenserListener</code>s 84 */ 85 StampDispenserState returnCoins(StampDispenser stampDispenser, 86 Set listeners) { 87 88 return this; 89 } 90 }
The Go4 book claims that the benefit of the state pattern is that it localizes state specific code. I.e., all the code for each state is in one place, the concrete subclass for the state. It therefore eliminates the switch statements, which can be difficult to maintain, especially if they are long because there's lots of states. In addition, if the state objects are stateless, they can be shared. Such stateless state objects, the Go4 book goes on to say, are, in fact, basically flyweights.
Which brings me to flyweights. If you look closely at the Has0State
class, you'll see that
its instances have no state. The Has0State
class, like all its sibling State
subclasses, has no instance variables. That makes Has0State
a flyweight, which sits at
the behavior end of the state-behavior spectrum.
The State
subclasses are flyweights: bundles of pure behavior.
Though flyweights can have some state too. Extrisic/Intrinsic. Extrinsic
state in this case is the StampDispenser
and the Set
of StampDispenserListener
s
passed to the add5
, add10
, and returnCoins
methods.
The main intent of flyweights as described in Go4 is to enable sharing of objects to
reduce the number of objects in a system. Has0State
is shared. It's
a singleton shared by all instances of StampDispenser
.
Represent the "behavior end" of a Behavior/State spectrum
Talk about intrinsic versus extrinsic state. Talk about a non-stateless flyweight.
My point is that at the flyweight end of the spectrum, the behavior of an object doesn't
depend anymore on an object's state, just its class. So the way you get interesting behavior
is by subsituting an object of some other class. I.e., I switch from a Has0State
to a Has5State
, and I get different behavior.
The dull comedian from the previous guideline was a flyweight. The comedian was dull because
every time you asked him to tell a joke, he told you the same joke. Because he didn't have
state to help him provide interesting behavior in his tellJoke
method, he always
did the same behavior:
1 package com.artima.examples.comedian.ex1; 2 3 /** 4 * A comedian that tells jokes. 5 */ 6 class Comedian { 7 8 /** 9 * Returns a joke as a <CODE>String</CODE>. 10 */ 11 public String tellJoke() { 12 13 return "Why did the chicken cross the road? To get to the other side."; 14 } 15 }
In the case of this kind of comedian, then, what you'd need to do to have an interesting
evening is invite all the one-joke comedians you know over to your house for a party, then
keep asking different people for a joke. Even if everyone only knows one joke, if you
have enough people to ask, you can have an enjoyable evening. This is what the
StampDispenser
in ex3 does. It keeps swapping out the current state object,
so that when it asks the current state to add10
, it isn't sure what's
going to happen (what will happen depends only on the class of the current state object),
which allows the StampDispenser
to provide interesting behavior to its
client.
This is why I say use flyweights to provide pluggable nuggets of behavior. Given that a flyweight has little or no state, its main utility comes in being able to unplug one flyweight an plug in another flyweight that has the same interface. The flyweights function as pluggable nuggets of behavior.
Moved this here from guideline 3, which was getting too big. Can reiterate that state is used to have interesting behavior, the point of 3, in this guideline.
By using their state to decide how to behave, service-oriented objects can have more interesting behavior. For example, if I ask you 50 times to tell a joke, and you always tell the same joke, that's kind of boring behavior. If a class has no state, then every time I ask an instance of that class to perform a service for me it behaves in exactly the same way:
Listing 6-2. A boring comedian.
1 package com.artima.examples.comedian.ex1; 2 3 /** 4 * A comedian that tells jokes. 5 */ 6 class Comedian { 7 8 /** 9 * Returns a joke as a <CODE>String</CODE>. 10 */ 11 public String tellJoke() { 12 13 return "Why did the chicken cross the road? To get to the other side."; 14 } 15 }
Better that the class have an arsenal of jokes, and a state variable indicating the next joke to tell:
Listing 6-3. A slightly less boring comedian.
1 package com.artima.examples.comedian.ex2; 2 3 /** 4 * A comedian that tells jokes. 5 */ 6 class Comedian { 7 8 private static final String jokes[] = { 9 "Why did the chicken cross the road? To get to the other side.", 10 "What's green and sings? Elvis Parsley.", 11 "What's the last thing to go through a bug's mind when it hits your windshield? Its butt.", 12 }; 13 14 private int nextJoke = 0; 15 16 /** 17 * Returns a joke as a <CODE>String</CODE>. 18 */ 19 public String tellJoke() { 20 21 int jokeIndex = nextJoke; 22 ++nextJoke; 23 if (nextJoke >= jokes.length) { 24 nextJoke = 0; 25 } 26 return jokes[jokeIndex]; 27 } 28 } 29
That's more interesting behavior,
because the object is using its state (the nextJoke
instance variable) to decide how to behave
(which joke to tell).
Sponsored Links
|