The Artima Developer Community
Sponsored Link

Guideline 5
Interface Design by Bill Venners
Use Flyweights as pluggable nuggets of behavior

Advertisement

Interface Design | Contents | Previous | Next

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 StampDispenserListeners 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



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use