You are on page 1of 8

Design Service-Oriented Objects that use their state to decide how to behave

In the Java virtual machine, an object's state is values of its instance variables. Its
behavior is the actions it takes when you invoke its instance methods. Its identity is the
address of the object image on the heap, which is available to programmers as its
reference. If two object's have identical state and behavior, the way you can tell them
apart is by comparing their references.
Granted, most object designs I have encountered have had both interesting state and
interesting behavior, as predicted by Booch's statement. I call this most common kind of
object Service-Oriented. I have often encountered objects, however, that have little or no
interesting behavior. These objects, which I call Messengers, are composed primarily of
state. On the other hand, I have on occasion encountered objects that have little or no
interesting state. These objects, dubbed Flyweights by the Design Patterns book, are
composed primarily of behavior.
With experience I came to realize is that there's really a spectrum between state and
behavior, and that every object design lands somewhere on the state-behavior spectrum.
Figure 3-1 shows this state-behavior spectrum stretched out along the x-axis, with the y-
axis showing the frequency of object designs. As Figure 3-1 shows, most object designs
tend to be Service-Oriented Objects, which have both state and behavior. Fewer object
designs are Messengers, which show up at the state end of the spectrum. Still fewer
object designs are Flyweights, which show up at the behavior end of the spectrum. Both
mutable and immutable objects can show up anywhere on this spectrum.

The day I was trying to think of an example of a service-oriented object for this book, I
visited the post office and bought stamps from a vending machine. I decided to use as an
example an object that models the behavior of an extremely simple stamp machine. Such
an object could be used in the control software of an actual physical incarnation of the
stamp machine. Here's a specification of requirements for such a machine:

Write control software for an automated stamp dispenser. The stamp dispenser accepts
only nickels (5 cents) and dimes (10 cents) and dispenses only 20 cent stamps. An LED
display on the stamp dispenser indicates to the user the total amount of money that has
been inserted so far. As soon as 20 or more cents has been inserted, a 20 cent stamp is
automatically dispensed and any change is returned. The only amounts that show up on
the display, therefore, are 0, 5, 10, and 15 cents. If a dime and a nickel have been
inserted, the display will indicate 15 cents. If the user then inserts another dime, the
stamp dispenser will dispense a 20 cent stamp, return a nickel, and change the display to
show 0 cents. In addition to a slot for inserting coins, a slot for dispensing stamps and
returning coins, and an LED display, the stamp dispenser also has a coin return lever.
When the user presses coin return, the stamp dispenser returns the amount of money
indicated on the display, and the display changes to show 0 cents.

An object-oriented solution to these requirements could include a class named


StampDispenser, that models the functionality of the real-world stamp dispenser, as
shown in Listing 3-1:

Listing 3-1. A stamp dispenser.

1 package com.artima.examples.stampdispenser.ex1;
2
3 import java.util.Set;
4 import java.util.Iterator;
5 import java.util.HashSet;
6
7 /**
8 * A stamp dispenser that accepts nickels and dimes and dispenses
9 * twenty cent stamps.
10 *
11 * @author Bill Venners
12 */
13 public class StampDispenser {
14
15 private final static int STAMP_VALUE = 20;
16 private int balance;
17 private Set listeners = new HashSet();
18
19 /**
20 * Constructs a new stamp dispenser with a starting current
value of zero.
21 */
22 public StampDispenser() {
23 }
24
25 /**
26 * Adds the specified stamp dispenser listener to receive stamp
dispenser events
27 * from this stamp dispenser. If <code>l</code> is
<code>null</code>, no exception
28 * is thrown and no action is performed. If <code>l</code> is
already registered
29 * as a listener, no action is performed.
30 */
31 public synchronized void
addStampDispenserListener(StampDispenserListener l) {
32
33 listeners.add(l);
34 }
35
36 /**
37 * Removes the specified stamp dispenser listener so that it no
longer
38 * receives stamp dispenser events from this stamp dispenser.
This method
39 * performs no function, nor does it throw an exception, if the
listener
40 * specified by the argument was not previously added to this
41 * component. If <code>l</code> is <code>null</code>, no
exception is
42 * thrown and no action is performed.
43 */
44 public synchronized void
removeStampDispenserListener(StampDispenserListener l) {
45
46 listeners.remove(l);
47 }
48
49 /**
50 * Add either 5 or 10 cents to the stamp dispenser. If the
amount added
51 * causes the current value to become or exceed 20 cents, the
price of
52 * a stamp, the stamp will be automatically dispensed. If the
stamp is
53 * dispensed, the amount of the current value after the stamp is
dispensed
54 * is returned to the client.
55 *
56 * @throws IllegalArgumentException if passed
<code>amount</code> doesn't
57 * equal either 5 or 10
58 */
59 public synchronized void add(int amount) {
60
61 if ((amount != 5) && (amount != 10)) {
62 throw new IllegalArgumentException();
63 }
64
65 balance += amount;
66
67 if (balance >= STAMP_VALUE) {
68
69 // Dispense a stamp and return any change
70 // balance - STAMP_VALUE is amount in excess of twenty
cents
71 // (the stamp price) to return as change. After
dispensing the stamp and
72 // returning any change, the new balance will be zero.
73 StampDispenserEvent event = new
StampDispenserEvent(this, balance - STAMP_VALUE, 0);
74 balance = 0;
75 fireStampDispensed(event, listeners);
76 }
77 else {
78
79 // Fire an event to indicate the balance has increased
80 StampDispenserEvent event = new
StampDispenserEvent(this, amount, balance);
81 fireCoinAccepted(event, listeners);
82 }
83 }
84
85 /**
86 * Returns coins. If the current value is zero, no action is
87 * performed.
88 */
89 public synchronized void returnCoins() {
90
91 // Make sure balance is greater than zero, because no event
should
92 // be fired if the coin return lever is pressed when the
stamp
93 // dispenser has a zero balance
94 if (balance > 0) {
95
96 // Return the entire balance to the client
97 StampDispenserEvent event = new
StampDispenserEvent(this, balance, 0);
98 balance = 0;
99 fireCoinsReturned(event, listeners);
100 }
101 }
102
103 /**
104 * Helper method that fires coinAccepted events.
105 */
106 private static void fireCoinAccepted(StampDispenserEvent event,
107 Set listeners) {
108
109 Iterator it = listeners.iterator();
110 while (it.hasNext()) {
111 StampDispenserListener l = (StampDispenserListener)
it.next();
112 l.coinAccepted(event);
113 }
114 }
115
116 /**
117 * Helper method that fires stampDispensed events.
118 */
119 private static void fireStampDispensed(StampDispenserEvent
event,
120 Set listeners) {
121
122 Iterator it = listeners.iterator();
123 while (it.hasNext()) {
124 StampDispenserListener l = (StampDispenserListener)
it.next();
125 l.stampDispensed(event);
126 }
127 }
128
129 /**
130 * Helper method that fires coinsReturned events.
131 */
132 private static void fireCoinsReturned(StampDispenserEvent event,
133 Set listeners) {
134
135 Iterator it = listeners.iterator();
136 while (it.hasNext()) {
137 StampDispenserListener l = (StampDispenserListener)
it.next();
138 l.coinsReturned(event);
139 }
140 }
141 }

The StampDispenser offers its primary services to clients via two public methods: add
and returnCoins. The StampDispenser also enables clients to be notified of important
events by registering themselves via the addStampDispenserListener method. Clients
can change their minds via the removeStampDispenser method. Here's the
StampDispenserListener interface:

Listing 3-2. Stamp dispenser listener.

1 package com.artima.examples.stampdispenser.ex1;
2
3 /**
4 * Listener interface for receiving stamp dispenser events.
5 */
6 public interface StampDispenserListener {
7
8 /**
9 * Invoked when a stamp has been dispensed. If coins
10 * have also been returned as change, the amount is
11 * indicated by the return value of
<CODE>getReturnedAmount</CODE>
12 * method of the passed <code>StampDispenserEvent</code>.
13 */
14 void stampDispensed(StampDispenserEvent e);
15
16 /**
17 * Invoked when coins have been returned as the result of
18 * the <code>returnCoins</code> method being invoked on
19 * a <code>StampDispenser</code>. Coins that are returned
20 * as change when a stamp is dispensed are reported via
21 * the event passed to <code>stampDispensed</code>.
22 */
23 void coinsReturned(StampDispenserEvent e);
24
25 /**
26 * Invoked when coins have been accepted but no stamp
27 * has been dispensed. A coin that causes a stamp to
28 * be dispensed does not generate a <code>coinsAccepted</code>
29 * method invocation, just a <code>stampDispensed</code>
30 * method invocation.
31 */
32 void coinAccepted(StampDispenserEvent e);
33 }

Were an instance of StampDispenser to be used to control a real life simple stamp


dispenser, the listeners would be responsible for actually returning coins, dispensing a
stamp, and changing the display that shows how much money has been inserted so far.
The client that invoked the add method would be code that knows that money was
inserted into the real slot. The client that invoked the returnCoins method would be
code that knows the return coins lever was pressed.

When a listener is notified, it receives an instance of StampDispenserEvent:

Listing 3-3. Stamp dispenser event.

1 package com.artima.examples.stampdispenser.ex1;
2
3 import java.util.EventObject;
4
5 /**
6 * Event that indicates a stamp dispenser has performed
7 * an action. The three kinds of actions that cause a
8 * stamp dispenser event to be propagated are:
9 * (1) accepting a coin, (2) dispensing a stamp,
10 * (3) returning coins.
11 */
12 public class StampDispenserEvent extends java.util.EventObject {
13
14 private int amountReturned;
15 private int balance;
16
17 /**
18 * Constructs a <code>StampDispenserEvent</code> with
19 * <code>amountReturned</code>, and <code>balance</code>.
20 *
21 * @param amountReturned the amount of money, if any,
22 * returned to the client, either as the result of
23 * a coin return or as change when dispensing a stamp.
24 * @param balance the amount of money, if any, remaining
25 * as the current balance of the stamp dispenser after
26 * this event has occurred.
27 * @throws IllegalArgumentException if balance is not one
28 * of 0, 5, 10, or 15; or if amountReturned is not one.
29 * of 0, 5, 10, or 15.
30 */
31 public StampDispenserEvent(StampDispenser source, int
amountReturned,
32 int balance) {
33
34 super(source);
35
36 if (balance != 0 && balance != 5 && balance != 10
37 && balance != 15) {
38
39 throw new IllegalArgumentException();
40 }
41
42 if (amountReturned != 0 && amountReturned != 5 &&
amountReturned != 10
43 && amountReturned != 15) {
44
45 throw new IllegalArgumentException();
46 }
47
48 this.amountReturned = amountReturned;
49 this.balance = balance;
50 }
51
52 /**
53 * Returns the amount of money returned to the client,
54 * expressed in units of American pennies.
55 */
56 public int getAmountReturned() {
57 return amountReturned;
58 }
59
60 /**
61 * Returns the current balance: the amount of money that has been
inserted
62 * into the stamp dispenser, but not returned via a coin return
63 * or consumed in exchange for a dispensed stamp. For example,
64 * if the <code>balance</code> is zero and a nickel is
65 * added, the <code>balance</code> in the resulting
66 * stamp dispenser event will be 5. If another dime is added,
67 * the <code>balance</code> in the resulting stamp dispenser
68 * event will be 15. If the <code>returnCoins</code> method is
then
69 * invoked on the stamp dispenser, the <code>balance</code>
70 * of the resulting stamp dispenser event will be 0.
71 */
72 public int getBalance() {
73 return balance;
74 }
75 }

A StampDispenserEvent contains information the listener can use to respond to the


event. For example, listeners can know how much money to return, if any, and what
amount should show up on the display.

Class StampDispenser illustrates the basic form of a Service-Oriented Object. Its


instance variables are private, so its accessible methods are the only way to manipulate
the state of the object. Is is called "service-oriented," because its contract with the client
is expressed in terms of services offered, which is behavior. It's contract is not expressed
in terms of data, which is state.

Service-oriented objects like StampDispenser have state, but they use their state to
decide how to behave when their methods are invoked. When the StampDispenser's add
method is invoked, for example, it uses its current balance to decide whether or not to
dispense a stamp, whether or not to return any change, and what new value to give to
balance. Similarly, the returnCoins method uses balance to decide whether or not to
return any money, and if so how much money to return.

The main point to take away from this guideline is that in the basic, service-oriented
object design, objects keep their state private, and expose only their behavior. The state
can be either mutable or immutable. The reason such objects have state is to help them
decide how to behave when called upon to perform a service. Thus, even though such
objects have both state and behavior, they are service-oriented, not data-oriented.

You might also like