The Artima Developer Community
Sponsored Link

Weblogs Forum
OOP Case Study: The Bank Account Class

24 replies on 2 pages. Most recent reply: Aug 20, 2005 9:50 PM by rajwant

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 24 replies on 2 pages [ 1 2 | » ]
Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

OOP Case Study: The Bank Account Class (View in Weblogs)
Posted: Nov 4, 2004 8:00 AM
Reply to this message Reply
Summary
The example of a bank account class is a canonical one in the object oriented programming (OOP) literature. Here are my thoughts on how the bank account problem should be approached.
Advertisement

The object-oriented bank account example is a common demonstration of OOP by attempting to model a bank account class. Eric Theriault referenced a talk on his blog by Kevlin Henney which brought the old chestnut to the forefront of my consciousness again. Here is the quote from Eric's blog which I find rather enlightened.

... one of my courses at OOPSLA has been with Kevlin Henney, and he reminds us that when developing object oriented software, the getters and setters are generally not the proper abstraction for an object. More specifically, his example was for a Bank Account, where the methods provided are getBalance() and setBalance(). While this is a very powerful interface, this is not the proper abstraction to use, especially since in this case, the object could change state between the initial get and the final set. As such, a better interface would be a method to acquire the current balance, a method to withdraw funds, and a method to deposit funds. In addition to being a more coherent interface, the business logic can easily be applied in this design.

There is no argument from me that Get and Set is a naive interface to a multi-threaded bank account class. It is pretty safe to expect that a bank account class is going to be multi-threaded, as most models of a transactional system probably should be.

The issue is, if we only provided locksafe Deposit / Withdraw, how do we deal with something like deposit interest? We could conceivably provide another function for DepositInterest. The next problem would be a bank fee scheme which depends on the amount of money in the account. Again this also has to be thread safe. But what happens is we are overloading the repsonsibility of the account class.

What makes the most sense is to keep the BankAccount class as simple as possible. Something like: ViewBalance, GetBalanceAndLock, SetBalanceAndUnlock. Strictly speaking nothing else is needed, all other functionality can be provided from that basis. More sophisticated functionality can be implemented in very simple account accessor classes. Withdraw and Deposit for instance chould be separated into a new class which could be descirbed as below:

TellerAccountAccessor {
  PayBill(amount) {
    cur = account.GetBalanceAndLock()
    account.SetBalanceAndUnlock(cur - amount);
  }
  Withdraw(amount) {
    cur = account.GetBalanceAndLock()
    account.SetBalanceAndUnlock(cur - amount);
  }
  Deposit(amount) {
    cur = account.GetBalanceAndLock()
    account.SetBalanceAndUnlock(cur + amount);
  }
  BankAccount account;
}

Since I brought up the concerns of interest and fees it would make sense to have another class which provides that functionality:

BankAccountAccessor {
  DeductFees(exemption, fees) {
    cur = account.GetBalanceAndLock();
    if (cur < exemption) {
      cur = cur - fees;
    }
    SetBalanceAndUnlock(cur);
  }
  DepositInterest(interest_rate) {
    cur = account.GetBalanceAndLock();
    interest = cur * interest_rate;      
    cur = cur + interest;
    SetBalanceAndUnlock(cur);
  }
  BankAccount account;
}

I invite the reader to share their thoughts on the subject. Even the simplest of basic object oriented designs can make for interesting and informative discussion.


Marcin Kowalczyk

Posts: 40
Nickname: qrczak
Registered: Oct, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 3:46 PM
Reply to this message Reply
This is similar to http://haskell.org/ghc/docs/latest/html/libraries/base/Control.Concurrent.MVar.html

Darren Oakey

Posts: 1
Nickname: bitzero
Registered: Nov, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 4:29 PM
Reply to this message Reply
I'm like where you're going with this, but think it can be improved. I always reckon we want to code as defensively as possible, and creating a lock externally is not something I think is safe, becaue we've got no control over when it's unlocked, whether it's unlocked and so on - what happens if an exception occurs?

In a world of lambda functions/anon delegates especially - I think you can make the bank account even simpler:
BankAccount
{
Balance {get;}
AdjustBalance( number AdjustFunction( number ))
}

Because you then have

TellerAccountAssessor
{
PayBill( amount)
{ account.AdjustBalance( {lambda x: x - amount}))
Withdraw( amount)
{ account.AdjustBalance( {lambda x: x - amount}))
Deposit( anmount)
{ account.AdjustBalance( {lambda x: x + amount}))
}

and the bank account has COMPLETE CONTROL over what's going on - it can decide when to lock, when to unlock, whether to roll back if an exception occurs and so on. Nobody can call it and forget anything - it's almost unbreakable. Even deadlocks on the same object can be trapped with a simple reentrancy check in AdjustBalance.

Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 4:37 PM
Reply to this message Reply
It seems that the abstraction you have here is:

getBalanceAndUnlock();
... // do stuff
setBalanceAndLock(newBalance);


You could have a method that looks like that. It would accept an object (or a block), pass it the balance, run it, and pass the return to setBalanceAndLock:


void updateWith(Updater updater) {
setBalanceAndLock(
updater.run(getBalanceAndLock()));
}


At that point, do we need locking, really?

Eric Y. Theriault

Posts: 1
Nickname: etheriau
Registered: Nov, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 4:53 PM
Reply to this message Reply
I agree with Darren Oakey here. In Christopher's proposed design, there is a major problem with the ownership of the locks, and the proposed workaround is to simply use wrapper classes that abstract out the functionality.

The only way that the proposed design could work is if the actually BankAccount class would be inaccessible to any the users, and that facade-like classes are used to provide the actual functionality.

But while going into that area, I would go more with Darren's solution. One advantage that the other functionality did have, however, is that custom algorithms could be applied, and as an alternative, I would propose the usage of an interface for applying custom calculations.

The real advantage of this approach is controlling the lock's scope. When you do not control the lock, debugging the system can be extremely difficult, since on deadlock, tracing through the system for a missing unlock can be more difficult, and testing your system for deadlocks ends up being for each individual class that uses the class.

Regarding Darren's concern about reentrancy, the recommended practice for public methods that lock objects is to only call private methods, so this issue could be avoided with a simple coding standard.

And for these reasons, I would prefer being a user of this class than the former class.



eyt*

Jared MacDonald

Posts: 13
Nickname: jared
Registered: Oct, 2002

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 7:39 PM
Reply to this message Reply
> The real advantage of this approach is controlling the
> lock's scope. When you do not control the lock, debugging
> the system can be extremely difficult, since on deadlock,
> tracing through the system for a missing unlock can be
> more difficult, and testing your system for deadlocks ends
> up being for each individual class that uses the class.

Right. There's a useful parallel here to database connections, where instead of locking and unlocking, we're opening and closing a connection. In its simplest form, roughly implemented:
interface Transaction {
  public Object execute(Connection conn);
}
 
class DatabaseUtils {
  public static Object doTransaction(Transaction t) {
    Connection conn = null;
    try {
      conn = openConnection();
      return t.execute(conn);
    }
    finally {
      conn.close();
    }
  }
}

Just as with the implementation of the back account class that doesn't leave it up to the caller to ensure locking and unlocking, using the above pattern, we prevent connection leaks.

This stuff is probably obvious to functional programmers, but as a Java programmer, it took me a while to realize how useful this pattern is.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 8:54 PM
Reply to this message Reply
Thank you for the interesting feedback here.

So if I understand correctly the approach advocated by everyone is in general to design the bank account class using a transactional system.

This is an excellent idea, and I think it can be used in conjunction with the class I described above. The point that is being brought up, is that it is important to protect against lock mismanagement in a multi-threaded evnironment.

I propose that this repsonsibility can be taken care of in a wrapper class. There are many other different patterns and techniques which can be used to help prevent against such abuse. Choosing one over another at too low of a level of abstraction, can lead to rigid designs. By using two classes it simplifies the implementation because we have one class representing the account in a very general and efficient way, whereas another class simply manages the locks. I find this kind of design much easier to work with.

What do you think, is it reasonable to separate the design into two parts?

Steve Love

Posts: 20
Nickname: essennell
Registered: Sep, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 4, 2004 11:22 PM
Reply to this message Reply
I don't see the problem with your first example, with Deposit and Withdraw methods.

The locking of the bank account should be transparent to users of it anyway - why expose it? - and interest being added or fees being taken are only specific cases of Deposit and Withdraw.

The fact that BankAccount lives (or does not live) in a multi-threaded environment is entirely irrelevant to the design of its interface.

If it needs to be thread-safe, then that is an implementation detail, taken care of inside the Deposit and Withdraw methods.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 5, 2004 7:24 AM
Reply to this message Reply
"interest being added or fees being taken are only specific cases of Deposit and Withdraw"

In order to deposit interest you have to lock the account, get the balance, compute the interest, deposit the interest, unlock the account. I don't see how that could be considered a specific case of deposit. It is a different set of actions.

"If it needs to be thread-safe, then that is an implementation detail, taken care of inside the Deposit and Withdraw methods."

Thre is no such thing as an "implementation detail". Every bit of code is in fact an implementation detail.

At the lowest level of representation we have a basic representations:

interface RawAccount {
  ViewBalance() : Double;
  SetBalance(Double amount); 
}

It could have just as easily been written:

interface RawAccount {
  ViewBalance() : Double;
  Withdraw(Double amount);
  Deposit(Double amount);
}

I will continue to the first approach for continuity with my original example. The next level of abstraction could be:

interface LockableAccount {
  ViewBalance() : Double;
  GetBalanceAndLock() : Double;
  SetBalanceAndUnlock(Double amount); 
}

This version introduces the concerns of locking. Upon that we could add another interface which safely manages the lock concerns:

interface TransactionableAccount {
  ExecuteTransaction(Transaction t);      
}

Each level of abstraction introduces new concerns into the software one by one rather than belending them all together into one class. Different levels of abstraction are appropriate for different situiations.

Darryl Mataya

Posts: 9
Nickname: dmataya
Registered: Nov, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 5, 2004 9:56 AM
Reply to this message Reply
One of the reasons I think a bank account class is a good teaching tool is that you can quickly introduce the messy but realistic problems and their solutions. Let’s challenge the assumption inherent in the proposed interest calculation – that indeed an account has to be locked for any reason. A more realistic interest deposit business rule is something like this:

interest = account.BalanceAsOf(datetime) * account.RateAsOf(datetime) * (business rule factors)
account.deposit(interest, effectivedatetime)

The asof methods compute balance, they need not attempt a “lock and get.”

I usually get a “what about performance” argument at some point when proposing a transaction based design. But of course there are numerous ways to cache activity at the object level so that a high amount of transaction activity does not degrade the performance of the object.

I believe any effective teaching tool has to rapidly move the student to robust and effective solutions while maintaining simplicity of design. From here you can introduce problems and request solutions to the issues this design raises, like what if a rogue system or hacker sends multiple requests very quickly – say for example when a pre-authorization request to debit your account from a debit card is made.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 5, 2004 10:45 AM
Reply to this message Reply
Clearly what Daryl proposes is attractive alternative to a lock and get approach for computing interest in many cases. It is a model which I hadn't previously considered. I agree in general that it would be easy to implement with sufficient efficiency for many purposes.

I wish to point out on the other hand that if I was to implement a system which had to compute and deposit the interest on several million accounts in as short an amount of time as possible, then the lock/get/set/unlock approach would be an entire order of magnitude faster.

I don't believe either approach is more correct than the other. At the highest level of abstraction it depends purely on the context.

Steve Love

Posts: 20
Nickname: essennell
Registered: Sep, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 6, 2004 2:52 AM
Reply to this message Reply
> In order to deposit interest you have to lock the account,
> get the balance, compute the interest, deposit the
> interest, unlock the account. I don't see how that could
> be considered a specific case of deposit. It is a
> different set of actions.

Why the need to lock the account having obtained a balance? Interest is calculated at a specific point in time (as are charges), so the fact that the balance has changed between getting the balance and calculating then depositing the interest is fine and dandy.

> Thre is no such thing as an "implementation detail". Every
> bit of code is in fact an implementation detail.

Well maybe our terminology differs, but I make clear distinction between Interface and Implementation, where Implementation Detail should be invisible from the Interface. Thus, the BankAccount interface does not expose the fact that it is transactionable, or lockable at all.

> Each level of abstraction introduces new concerns into the
> software one by one rather than belending them all
> together into one class. Different levels of abstraction
> are appropriate for different situiations.

Hmmm. I don't agree that lockability is a level of abstraction pertinent to the interface of bank account; it is certainly the responsibility of the account object to ensure safe (for some value of safe according to the application) access to its innards, but it is also the object's responsibility to hide the detail of how it does it from the user. That's what abstraction means - ignoring unimportant detail.

The trick is getting to the abstraction - what detail can be safely ignored. Clearly, the fact that an account has a balance is an important detail; that it locks the balance and prevents writing while reading it is not.

Keith Ray

Posts: 658
Nickname: keithray
Registered: May, 2003

Re: OOP Case Study: The Bank Account Class Posted: Nov 6, 2004 7:22 AM
Reply to this message Reply
Consider a back (or credit card) account that charges or credits interest based on the daily balance. They're not going to compute that interest every day, they're going to do that once a month when sending you your account statement / credit-card bill. What they are going to do is keep a log of transactions, and then figure out what the balance of the account is at midnight each day based on that log. [They're also going to print out that log in the account statement/bill. ]

How is your design going to change based on these requirements?

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 6, 2004 10:41 PM
Reply to this message Reply
In response to Keith's design requirements I would propose a transactional based design similar to what Darryl described and using connections as Jared described. I would implement the following interfaces (written in psuedo-java):

interface IRawAccount {
  public {
    int GetBalance();
    void SetBalance(int amount);
  }
}
 
interface ILockableAccount extends IRawAccount{
  public {
    void Lock();
    void Unlock();
  }
}
 
interface IAccount {
  public {
    bool ExecuteTransaction(ITransaction);
    ITransactionLog GetLog();
    int GetBalanceAsOf(DateTime date);
    int GetBalanceNow();
  }
  // note: an implementation would likely use
  // a class implementing ILockableAccount
}
 
interface IAccountConnection
extends IRawAccount {
  public {
    IAccount GetAccount();
    bool IsValid();
    void Invalidate();
  }
}
 
interface ITransaction {
  public {
    bool Execute(IAccountConnection);
    IAccount GetAccount();
    DateTime DateTimeExecuted();
    bool ExecutionSuccess();
    String TransactionCode();
    int OldBalance();
    int NewBalance();
  }
}
 
interface ITransactionLog
extends IArray<ITransaction> {
  public {
    void AddTransaction(ITransaction);
  }
}
 
interface IWithdrawal
extends ITransaction { }
 
interace IDeposit
extends ITransaction { }
 
interface IBillPayment
extends ITransaction { }
 
interface IInterestDeposit
extends ITransaction { }
 
interface IDeductCharges
extends ITransaction { }
 
interface IBalanceAdjustment
extends ITransaction { }
 
interface ITeller {
  public {
    bool Withdraw(int amount);
    void Deposit(int amount);
    bool PayBill(int amount);
  }
}
What are your thoughts on the subject? Is the intended implementation clear from these interface?

James Emerton

Posts: 5
Nickname: ephelon
Registered: Oct, 2004

Re: OOP Case Study: The Bank Account Class Posted: Nov 8, 2004 3:56 PM
Reply to this message Reply
I'm a C++ programmer, and generally disinclined to lots of excessive objects and interfaces. Much of the interface being proposed in Chris' last post seems to be superfluous.

I believe that your design should model the business rules as closely as possible. This allows the most opportunity for enhancements without a library becoming somewhat arcane.

Bank accounts do not have their balance set to a specific value. When I go to a bank machine to deposit my cheque, I do not tell the machine how much money is going to be in my account, I tell it how much I am adding. No needs for the interface to expose any locking.

A design should also meet the criteria as concisely as possible. Do not add interfaces for every transaction type unless you have demonstrated a need for that kind of flexibilty. If you don't need it, all you bought yourself was more complexity to manage.

A bank transaction has only a few basic attributes. Time, type, description, amount, and possibly a confirmation number. There is no difference in how different transaction types are applied to an account. Even a withdrawal is just a negative deposit.

Also, the issue of connections is purely technical, and belongs in your data layer. I just need some way to get an account instance. (Some kind of account manager object) If a connection becomes invalid, throw an exception.

I do not see the management of locks internally as a performance hit during operations such as the calculation of interest. Interest is calculated daily, based on the historical balace at some time (midnight?) for each day of the month. Only one lock is required to actually write the transaction.

Maybe you don't really need locking at all. If you're using a relativly modern DB, the entire procedure of writing a transaction entry and updating the balance can be made atomic.

Flat View: This topic has 24 replies on 2 pages [ 1  2 | » ]
Topic: The Harry Potter Theory of Programming Language Design Previous Topic   Next Topic Topic: The Static Function Pointer Table Polymorphism Pattern

Sponsored Links



Google
  Web Artima.com   

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