The Artima Developer Community

Designing with Patterns Workshop
The Event Generator Idiom

Event Generator Idiom


Step 1. Define Event Category Classes

 1 package com.artima.examples.account.ex4;
 2
 3 import java.util.EventObject;
 4
 5 /**
 6 * Event that indicates an overdraft has either been loaned to a client or
 7 * repaid by a client during a withdrawal or deposit transaction on an
 8 * <code>Account</code>.
 9 */
10 public class OverdraftEvent extends java.util.EventObject {
11
12     private long overdraft;
13     private long amount;
14
15     /**
16     * Constructs an <code>OverdraftEvent</code> with the passed
17     * <code>source</code>, and <code>overdraft</code>.
18     *
19     * @param source the source of the event
20     * @param overdraft the current amount of overdraft outstanding in the
21     *     <code>OverdraftAccount</code>
22     * @throws IllegalArgumentException if either of <code>overdraft</code>
23     *     or <code>amount</code>, passed as parameters to this constructor,
24     *     are less than or equal to zero.
25     */
26     public OverdraftEvent(OverdraftAccount source,
27         long overdraft, long amount) {
28
29         super(source);
30
31         if (overdraft <= 0 || amount <= 0) {
32             throw new IllegalArgumentException();
33         }
34
35         this.overdraft = overdraft;
36         this.amount = amount;
37     }
38
39     /**
40     * Returns the current overdraft, the amount of of overdraft after the
41     * transaction that caused this event to be propagated.
42     *
43     * @returns the current overdraft
44     */
45     public long getOverdraft() {
46         return overdraft;
47     }
48
49     /**
50     * Returns the amount of money either loaned to the client or repaid to
51     * the bank during the transaction that caused this event to be
52     * propagated.
53     *
54     * @returns the amount loaned or repaid
55     */
56     public long getAmount() {
57         return amount;
58     }
59 }

Step 2. Define Listener Interfaces

 1 package com.artima.examples.account.ex4;
 2
 3 import java.util.EventListener;
 4
 5 /**
 6 * Listener interface for receiving overdraft events.
 7 */
 8 public interface OverdraftListener extends EventListener {
 9
10     /**
11     * Invoked when an overdraft has occurred. This method will
12     * be invoked on listeners whenever the bank loans money
13     * to a client that has requested a withdrawal of more funds
14     * than is available in the account's balance.
15     */
16     void overdraftOccurred(OverdraftEvent e);
17
18     /**
19     * Invoked when some or all of the outstanding overdraft
20     * that a bank has loaned to a client is repaid.
21     */
22     void overdraftRepaid(OverdraftEvent e);
23 }

Step 3. Define Adapter Classes (Optional)

 1 package com.artima.examples.account.ex4;
 2
 3 /**
 4 * An abstract adapter class for receiving overdraft events. The methods in
 5 * this class are empty. This class exists as convenience for listener
 6 * objects.
 7 */
 8 public abstract class OverdraftAdapter implements OverdraftListener {
 9
10     /**
11     * Invoked when an overdraft has occurred. This method will
12     * be invoked on listeners whenever the bank loans money
13     * to a client that has requested a withdrawal of more funds
14     * than is available in the account's balance.
15     *
16     * @param e the overdraft event object
17     */
18     public void overdraftOccurred(OverdraftEvent e) {
19     }
20
21     /**
22     * Invoked when some or all of the outstanding overdraft
23     * that a bank has loaned to a client is repaid.
24     *
25     * @param e the overdraft event object
26     */
27     public void overdraftRepaid(OverdraftEvent e) {
28     }
29 }

Step 4. Define Event Generator Classes

  1 package com.artima.examples.account.ex4;
  2
  3 /**
  4 * Represents a bank account with overdraft protection. Instances of this
  5 * class are instantiated with a specified maximum overdraft. If a client
  6 * attempts to withdraw more than the current account balance, the bank may
  7 * loan the amount in excess of the balance to the client. The overdraft
  8 * maximum passed to an <code>OverdraftAccount</code>'s constructor is the
  9 * maximum amount the bank will lend to the client in this manner. When a
 10 * client makes a deposit, the bank will pay itself back first before
 11 * increasing the account's balance.
 12 *
 13 * <p>
 14 * Money is stored in this account in integral units. Clients can use this
 15 * account to store any kind of value, such as money or points, etc. The
 16 * meaning of the integral units stored in this account is a decision of the
 17 * client that instantiates the account. The maximum amount of units that can
 18 * be stored as the current balance of an <code>OverdraftAccount</code> is
 19 * Long.MAX_VALUE.
 20 */
 21 public class OverdraftAccount implements Account, OverdraftEventGenerator {
 22
 23     /**
 24     * Helper back-end <code>BasicAccount</code> object
 25     */
 26     private BasicAccount account = new BasicAccount();
 27
 28     /**
 29     * Helper back-end <code>OverdraftEventManager</code> object
 30     */
 31     private OverdraftEventManager eventMan
 32         = new OverdraftEventManager();
 33
 34     /**
 35     * The maximum amount the bank will loan to the client.
 36     */
 37     private final long overdraftMax;
 38
 39     /**
 40     * The current amount the bank has loaned to the client which has not yet
 41     * been repaid. This value must at all times be greater than or equal to
 42     * zero, and less than or equal to <code>overdraftMax</code>. If this
 43     * value is greater than zero, then the balance of the helper
 44     * <code>BasicAccount</code> (referenced from the <code>account</code>
 45     * instance variable) must be exactly zero.
 46     */
 47     private long overdraft;
 48
 49     /**
 50     * Constructs a new <code>OverdraftAccount</code> with the passed
 51     * overdraft maximum.
 52     *
 53     * @param overdraftMax the maximum amount the bank will loan to the
 54     *     client
 55     */
 56     public OverdraftAccount(long overdraftMax) {
 57         this.overdraftMax = overdraftMax;
 58     }
 59
 60     /**
 61     * Returns the current overdraft, the amount the bank has loaned to the
 62     * client that has not yet been repaid.
 63     *
 64     * @return the current overdraft
 65     */
 66     public long getOverdraft() {
 67         return overdraft;
 68     }
 69
 70     /**
 71     * Returns the overdraft maximum, the maximum amount the bank will allow
 72     * the client to owe it. For each instance of
 73     * <code>OverdraftAccount</code>, the overdraft maximum is constant.
 74     *
 75     * @return the overdraft maximum
 76     */
 77     public long getOverdraftMax() {
 78         return overdraftMax;
 79     }
 80
 81     /**
 82     * Gets the current balance of this <code>OverdraftAccount</code>.
 83     *
 84     * @return the current balance
 85     */
 86     public long getBalance() {
 87         return account.getBalance();
 88     }
 89
 90     /**
 91     * Withdraws the passed amount from this <code>OverdraftAccount</code>.
 92     * If the passed amount is less than or equal to the current balance, all
 93     * withdrawn funds will be taken from the balance, and the balance will
 94     * be decremented by the passed amount. If the passed amount exceeds the
 95     * current balance, the bank may loan the client the difference. The bank
 96     * will make the loan only if the difference between the passed amount
 97     * and the balance (the shortfall) is less than or equal to the available
 98     * overdraft. The available overdraft is equal to the current overdraft
 99     * (the amount already loaned to the client and not yet repaid),
100     * subtracted from the overdraft maximum, which is passed to the
101     * constructor of any <code>OverdraftAccount</code>.
102     *
103     * <p>
104     * If the passed amount is less than or equal to the current balance, the
105     * <code>withdraw</code> method decrements the current balance by the
106     * passed amount and returns the passed amount. If the passed amount is
107     * greater than the current balance, but the passed amount minus the
108     * current balance is less than or equal to the available overdraft, the
109     * <code>withdraw</code> method sets the current balance to zero, records
110     * the loan, and returns the requested amount. Otherwise, the passed
111     * amount minus the current balance exceeds the available overdraft, so
112     * the <code>withdraw</code> method throws
113     * <code>InsufficientFundsException</code>.
114     *
115     * Subclasses must withdraw at least the passed amount, but may
116     * effectively withdraw more. For example, if a subclass includes a
117     * notion of a withrawal fee, the subclass's implementation of this
118     * method may charge that fee by decrementing it from the account at the
119     * time of withdrawal.
120     *
121     * @param amount amount to withdraw
122     * @return amount withdrawn from the <code>OverdraftAccount</code>
123     * @throws InsufficientFundsException if the
124     *     <code>OverdraftAccount</code> contains insufficient funds for the
125     *     requested withdrawal
126     * @throws IllegalArgumentException if requested withdrawal amount is
127     *     less than or equal to zero.
128     */
129     public long withdraw(long amount) throws InsufficientFundsException {
130
131         if (amount <= 0) {
132             throw new IllegalArgumentException();
133         }
134
135         long bal = account.getBalance();
136         if (bal >= amount) {
137
138             // Balance has sufficient funds, so just take the
139             // money from the balance.
140             return account.withdraw(amount);
141         }
142
143         long shortfall = amount - bal;
144         long extraAvailable = overdraftMax - overdraft;
145
146         if (shortfall > extraAvailable) {
147             throw new InsufficientFundsException(shortfall
148                 - extraAvailable);
149         }
150         overdraft += shortfall;
151         account.withdraw(amount - shortfall);
152
153         OverdraftEvent event = new OverdraftEvent(this, overdraft,
154             shortfall);
155         eventMan.fireOverdraftOccurred(event);
156
157         return amount;
158     }
159
160     /**
161     * Deposits the passed amount into the <code>OverdraftAccount</code>. If
162     * the current overdraft is zero, the balance will be increased by the
163     * passed amount. Otherwise, the bank will attempt to pay off the
164     * overdraft first, before increasing the current balance by the amount
165     * remaining after the overdraft is repaid, if any.
166     *
167     * <p>
168     * For example, if the balance is 0, the overdraft is 100, and the
169     * <code>deposit</code> method is invoked with a passed
170     * <code>amount</code> of 50, the bank would use all 50 of those monetary
171     * units to pay down the overdraft. The overdraft would be reduced to 50
172     * and the balance would remain at 0. If subsequently, the client
173     * deposits another 100 units, the bank would use 50 of those units to
174     * pay off the overdraft loan and direct the remaining 50 into the
175     * balance. The new overdraft would be 0 and the new balance would be
176     * 50.
177     *
178     * Subclasses may effectively deposit more or less than the passed amount
179     * into the <code>OverdraftAccount</code>. For example, if a subclass
180     * includes a notion of funds matching, the subclass implementation of
181     * this method may match some or all of the deposited amount at the time
182     * of deposit, effectively increasing the deposited amount. Or, if a
183     * subclass includes the notion of a deposit fee, the subclass's
184     * implementation of this method may charge that fee by decrementing it
185     * from the account at the time of deposit, effectively reducing the
186     * deposited amount.
187     *
188     * @param amount amount to deposit
189     * @throws ArithmeticException if requested deposit would cause the
190     *     balance of this <code>OverdraftAccount</code> to exceed
191     *     Long.MAX_VALUE.
192     * @throws IllegalArgumentException if requested withdrawal amount is
193     *     less than or equal to zero.
194     */
195     public void deposit(long amount) {
196
197         if (amount <= 0) {
198             throw new IllegalArgumentException();
199         }
200
201         if (overdraft > 0) {
202
203             long amountRepaid = 0;
204
205             if (amount < overdraft) {
206                 amountRepaid = amount;
207                 overdraft -= amount;
208             }
209             else {
210                 long diff = amount - overdraft;
211                 amountRepaid = diff;
212                 overdraft = 0;
213                 account.deposit(diff);
214             }
215
216             OverdraftEvent event = new OverdraftEvent(this, overdraft,
217                 amountRepaid);
218             eventMan.fireOverdraftRepaid(event);
219         }
220         else {
221             account.deposit(amount);
222         }
223     }
224
225     /**
226     * Adds the specified overdraft listener to receive overdraft events from
227     * this <code>OverdraftAccount</code>. If <code>l</code> is
228     * <code>null</code>, no exception is thrown and no action is performed.
229     * If <code>l</code> is already registered as a listener, no action is
230     * performed.
231     *
232     * @param l the <code>OverdraftEventListener</code> to add
233     */
234     public void addOverdraftListener(OverdraftListener l) {
235
236         eventMan.addOverdraftListener(l);
237     }
238
239     /**
240     * Removes the specified overdraft listener so that it no longer receives
241     * overdraft events from this <code>OverdraftAccount</code>. This method
242     * performs no function, nor does it throw an exception, if the listener
243     * specified by the argument was not previously added to this component.
244     * If <code>l</code> is <code>null</code>, no exception is thrown and no
245     * action is performed.
246     *
247     * @param l the <code>OverdraftEventListener</code> to remove
248     */
249     public void removeOverdraftListener(OverdraftListener l) {
250
251         eventMan.removeOverdraftListener(l);
252     }
253 }

  1 package com.artima.examples.account.ex4;
  2
  3 import java.util.Set;
  4 import java.util.Iterator;
  5 import java.util.HashSet;
  6
  7 /**
  8 * A class that manages registration and unregistration of
  9 * <code>OverdraftListener</code>s and the firing of
 10 * <code>OverdraftEvent</code>s.
 11 *
 12 * @author Bill Venners
 13 */
 14 class OverdraftEventManager {
 15
 16     /**
 17     * Unsynchronized <code>HashSet</code> to which listeners are added and
 18     * removed via the synchronized methods <code>addOverdraftListener</code>
 19     * and <code>removeOverdraftListener</code> methods.
 20     */
 21     private HashSet listeners = new HashSet();
 22
 23     /**
 24     * Clone of the <code>listeners</code> <code>HashSet</code>, which is
 25     * used by the <code>fireOverdraftOccurred</code> and
 26     * <code>fireOverdraftRepaid</code> methods to propagate events. This
 27     * <code>HashSet</code> always contains a most recent snapshot of the
 28     * <code>listeners</code> <code>HashSet</code>, but this
 29     * <code>HashSet</code> is never modified, only replaced. Because this
 30     * <code>HashSet</code> is never modified, the fire methods can iterate
 31     * through the set without synchronization. This implementation approach
 32     * is geared towards providing optimum performance for the expected run
 33     * time usage in which adding and removing listeners happens less
 34     * frequently than firing events to those listeners.
 35     */
 36     private Set listenersClone = new HashSet();
 37
 38     /**
 39     * Constructs a new <code>OverdraftEventManager</code>. The
 40     * <code>OverdraftEventManager</code> starts its life with an empty
 41     * listeners list.
 42     */
 43     public OverdraftEventManager() {
 44     }
 45
 46     /**
 47     * Adds the specified overdraft listener to receive overdraft events. If
 48     * <code>l</code> is <code>null</code>, no exception is thrown and no
 49     * action is performed. If <code>l</code> is already registered as a
 50     * listener, no action is performed.
 51     *
 52     * @param l the <code>OverdraftEventListener</code> to add
 53     */
 54     public synchronized void addOverdraftListener(OverdraftListener l) {
 55
 56         listeners.add(l);
 57         listenersClone = (Set) listeners.clone();
 58     }
 59
 60     /**
 61     * Removes the specified overdraft listener so that it no longer receives
 62     * overdraft events. This method performs no function, nor does it throw
 63     * an exception, if the listener specified by the argument was not
 64     * previously added to this component. If <code>l</code> is
 65     * <code>null</code>, no exception is thrown and no action is performed.
 66     *
 67     * @param l the <code>OverdraftEventListener</code> to remove
 68     */
 69     public synchronized void removeOverdraftListener(OverdraftListener l) {
 70
 71         listeners.remove(l);
 72         listenersClone = (Set) listeners.clone();
 73     }
 74
 75     /**
 76     * Fires overdraftOccurred events to registered listeners.
 77     *
 78     * @param event the <code>OverdraftEvent</code> to propagate
 79     */
 80     public void fireOverdraftOccurred(OverdraftEvent event) {
 81
 82         Iterator it = listenersClone.iterator();
 83         while (it.hasNext()) {
 84             OverdraftListener l = (OverdraftListener) it.next();
 85             l.overdraftOccurred(event);
 86         }
 87     }
 88
 89     /**
 90     * Fires overdraftRepaid events to registered listeners.
 91     *
 92     * @param event the <code>OverdraftEvent</code> to propagate
 93     */
 94     public void fireOverdraftRepaid(OverdraftEvent event) {
 95
 96         Iterator it = listenersClone.iterator();
 97         while (it.hasNext()) {
 98             OverdraftListener l = (OverdraftListener) it.next();
 99             l.overdraftRepaid(event);
100         }
101     }
102 }

Discussion


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