The Artima Developer Community
Sponsored Link

Interface Design by Bill Venners
Use composition to enlist the help of other objects

Advertisement

Interface Design | Contents | Previous | Next

Using Composition


Yet Another Account Object

  1 package com.artima.examples.account.ex2;
  2
  3 /**
  4 * Represents a bank account with overdraft protection. Instances
  5 * of this class are instantiated with a specified maximum
  6 * overdraft. If a client attempts to withdraw more than the
  7 * current account balance, the bank may loan the amount in
  8 * excess of the balance to the client. The overdraft maximum
  9 * passed to an <code>OverdraftAccount</code>'s constructor
 10 * is the maximum amount the bank will lend to the client in
 11 * this manner. When a client makes a deposit, the bank will
 12 * pay itself back first before increasing the account's balance.
 13 *
 14 * <p>
 15 * Money is stored in this account in integral units. Clients
 16 * can use this account to store any kind of value, such as money
 17 * or points, etc. The meaning of the integral units stored in
 18 * this account is a decision of the client that instantiates the
 19 * account. The maximum amount of units that can be stored as
 20 * the current balance of an <code>Account</code> is Long.MAX_VALUE.
 21 */
 22 public class OverdraftAccount {
 23
 24     /**
 25     * Helper back-end object
 26     */
 27     private Account account = new Account();
 28
 29     /**
 30     * The maximum amount the bank will loan to the client.
 31     */
 32     private final long overdraftMax;
 33
 34     /**
 35     * The current amount the bank has loaned to the client
 36     * which has not yet been repaid. This must be zero to
 37     * overdraftMax.
 38     */
 39     private long overdraft;
 40
 41     /**
 42     * Constructs a new <code>OverdraftAccount</code> with the
 43     * passed <code>overdraftMax</code>.
 44     *
 45     * @param overdraftMax the maximum amount the bank will loan
 46     *     to the client
 47     */
 48     public OverdraftAccount(long overdraftMax) {
 49         this.overdraftMax = overdraftMax;
 50     }
 51
 52     /**
 53     * Returns the current overdraft, the amount the bank has
 54     * loaned to the client that has not yet been repaid.
 55     *
 56     * @returns the current overdraft
 57     */
 58     public long getOverdraft() {
 59         return overdraft;
 60     }
 61
 62     /**
 63     * Returns the overdraft maximum, the maximum amount the
 64     * bank will allow the client to owe it. For each instance
 65     * of <code>OverdraftAccount</code>, the overdraft maximum
 66     * is constant.
 67     *
 68     * @returns the overdraft maximum
 69     */
 70     public long getOverdraftMax() {
 71         return overdraftMax;
 72     }
 73
 74     /**
 75     * Gets the current balance of this <code>OverdraftAccount</code>
 76     *
 77     * @returns the current balance
 78     */
 79     public long getBalance() {
 80         return account.getBalance();
 81     }
 82
 83     /**
 84     * Withdraws exactly the passed amount from the
 85     * <code>Account</code>. If the passed amount is
 86     * less than or equal to the current balance, all withdrawn
 87     * funds will be taken from the balance, and the balance
 88     * will be decremented by the passed amount. If the passed amount
 89     * exceeds the current balance, the bank may loan the client the
 90     * difference. The bank will make the loan only if the difference
 91     * between the passed amount and the balance is less than or equal to
 92     * the available overdraft. The available overdraft is equal to
 93     * the current overdraft (the amount already loaned to the client and
 94     * not yet repaid), subtracted from the overdraft maximum, which
 95     * is passed to the constructor of any <code>OverdraftAccount</code>.
 96     *
 97     * <p>
 98     * If the passed amount less the current balance is less than or equal
 99     * to the available overdraft, the <code>withdraw</code> method returns
100     * the requested amount, sets the current balance to zero, and records
101     * the loan. Otherwise, if the passed amount less the current balance
102     * exceeds the available overdraft, the <code>withdraw</code> method throws
103     * <code>InsufficientFundsException</code>.
104     *
105     * @param amount amount to withdraw
106     * @returns amount withdrawn from the <code>Account</code>
107     * @throws InsufficientFundsException if the <code>Account</code>
108     *     contains insufficient funds for the requested withdrawal
109     */
110     public long withdraw(long amount)
111         throws InsufficientFundsException {
112
113         long balance = account.getBalance();
114         if (balance >= amount) {
115
116             // Balance has sufficient funds, just take the
117             // money from the balance.
118             balance -= amount;
119             return amount;
120         }
121
122         long shortfall = amount - balance;
123         long extraAvailable = overdraftMax - overdraft;
124
125         if (shortfall > extraAvailable) {
126             throw new InsufficientFundsException(shortfall - extraAvailable);
127         }
128         overdraft += shortfall;
129         account.withdraw(amount - shortfall);
130
131         return amount;
132     }
133
134     /**
135     * Deposits exactly the passed amount into the <code>Account</code>.
136     * If the current overdraft is zero, the balance will be increased
137     * by the passed amount. Otherwise, the bank will attempt to pay
138     * off the overdraft first, before increasing the current balance
139     * by the amount remaining after the overdraft is repaid, if any.
140     *
141     * <p>
142     * For example, if the balance is 200, the overdraft is 100, and the
143     * <code>deposit</code> method is invoked with a passed <code>amount</code>
144     * of 50, the bank would use all 50 of those monetary units to pay down
145     * the overdraft. The overdraft would be reduced to 50 and the balance would
146     * remain at 200. If subsequently, the client deposits another 100 units,
147     * the bank would use 50 of those units to pay off the overdraft loan and
148     * direct the remaining 50 into the balance. The new overdraft would
149     * be 0 and the new balance would be 250.
150     *
151     * @param amount amount to deposit
152     * @throws ArithmeticException if requested deposit would cause the
153     *     balance of this <code>Account</code> to exceed Long.MAX_VALUE.
154     */
155     public void deposit(long amount) {
156         if (overdraft > 0) {
157             if (amount < overdraft) {
158                 overdraft -= amount;
159             }
160             else {
161                 long diff = amount - overdraft;
162                 overdraft = 0;
163                 account.deposit(diff);
164             }
165         }
166         else {
167             account.deposit(amount);
168         }
169     }
170 }

Another relationship similar to composition is aggregation. Like composition, the aggregation relationship entails a front-end object holding references in instance variables to back-end objects. The difference is intent. In a composition relationship, the front-end objects enlist the help of the back-end objects to fulfill the front-end's semantic contract. In an aggregation relationship, front-end objects merely serve as containers for back-end objects. Front-end objects in an aggregation relationship don't invoke methods on back-ends, they just hold them.

In a non-garbage collected language, such as C++, it is more important to differentiate between composition and aggregation. Roughly speaking, if a back-end object is in a composition relationship in C++, the back-end should in general be deleted by the destructor of the front-end. By contrast, if a back-end object is held in an aggregation relationship in C++, the back-end object should in general survive the destruction of the front-end. Given that Java objects are never explicitly destroyed, just released to the whims of the garbage collector, the composition/aggregation distinction is not as important in Java.

On the other hand, it is important in Java to differentiate between composition and aggregation when implementing the clone method. Mutable back-end objects held in a composition relationship should be cloned along with the front-end object. In an aggregation relationship, by contrast, back-end objects should not be cloned. Put another way, composition implies deep copy; aggregation implies shallow copy. This distinction is discussed in guideline ??? and the Clonable Object Recipe in Appendix A.

composition sometimes models the HAS-A relationship, but not always. Here mention why I avoided HAS-A, that design patterns give a much richer discussion of composition relationships -- see chapter 4.

Discuss aggregation in that you are holding onto objects for the client, but not delegating to those objects yourself. Mention the collections stuff, messengers for multi-valued returns, arrays, tuples.


Sponsored Links



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