Playing OO design

Richard Suchenwirth 2002-10-06 - When playing OO, memories of an OO design training (in C++ at that time) came up again, and I wanted to test the tiny OO system's ability to handle slightly more complex systems, for example a bank simulation. The code below requires the class* procedures from Playing OO (with the "uplevel 1" commented out - the GC mechanism appears to be not yet ready for practical use), and besides should be pretty self-explanatory:

 #-------------------------------- Definitions
 class Bank     {}  customers {}  accounts {} bookings {} atms {}
 class Customer {} name ""  address ""  accounts {}
 class Account  {} customer ""  balance 0.00 sBalance 0.00 bookings {}
 class Booking  {} date ""  amount 0.00  from ""  to ""  text ""
 class ATM      {} bank ""  location ""  content 0.00

 proc  Bank::newCustomer {self customer} {
    $self: lappend customers $customer
    set customer
 }
 proc  Bank::newAccount {self customer} {
    set no [string range [clock clicks] end-5 end] ;# arbitrary number
    Account $no customer $customer
    $self:     lappend accounts $no
    $customer: lappend accounts $no
    set no     ;# return the generated account number
 }
 proc  Bank::deposit {self account amount} {
    if {[$self: lsearch \$accounts $account]<0} {error "no such account"}
    if {$amount < 1.00} {error "sorry: minimum deposit $ 1.00"}
    set b [Booking #auto date today amount $amount from cash to $account]
    $self:    lappend bookings $b
    $account: lappend bookings $b
    $self:    set balance [expr [$self:    set balance] + $amount]
    $account: set balance [expr [$account: set balance] + $amount]
 }
 proc  Bank::withdraw {self account amount {text ""}} {
    if {[$self: lsearch \$accounts $account]<0} {error "no such account"}
    set have [$account: set balance]
    if {$amount > $have} {error "sorry: insufficient funds"}
    if {$amount > [$self: set balance]} {error "sorry: we're bankrupt"}
    set b [Booking #auto date today amount $amount to cash \
        from $account text $text]
    $self:    lappend bookings $b
    $account: lappend bookings $b
    $self:    set balance [expr [$self:    set balance] - $amount]
    $account: set balance [expr [$account: set balance] - $amount]
 }
 proc  Bank::transfer {self from to amount {text ""}} {
    set b [Booking #auto date today amount $amount from $from \
        to $to text $text]
    $self: lappend bookings $b
    $from: lappend bookings $b
    $to:  lappend bookings $b
    $from: set balance [expr [$from: set balance] - $amount] 
    $to:  set balance [expr [$to:   set balance] + $amount]    
 }
 proc  Bank::statement {self account} {
    set res "\n[$self: set name] * Statement for Account $account"
    append res "\nCustomer: [[$account: set customer]: set name]"
    set sb [$account: set sBalance]
    append res "\n[format %-40s%9.2f {Starting balance:} $sb]"
    foreach booking [$account: set bookings] {
        set amount [$booking: set amount]
        if {[$booking: set from] == $account} {
            set amount -$amount
            set text "transfer to [$booking: set to]"
        } else {
            set text "transfer from [$booking: set from]"
        }
        if {[$booking: set text] != ""} {set text [$booking: set text]}
        append res \n[format %-40s%9.2f $text $amount]
    }
    set balance [$account: set balance]
    $account: set sBalance $balance
    $account: {set bookings {}} ;# will be printed only one time
    append res "\n\n[format %-40s%9.2f {Current balance:} $balance]"
 }
 proc ATM::refill {self amount} {
    $self: set content [expr [$self: set content] + $amount]
 }
 proc ATM::withdraw {self account amount} {
    set content [$self: set content]
    if {$amount > $content} {
        puts "Sorry, can only provide $content"
        set amount $content
    }
    set text "$self at [$self: set location]"
    [$self: set bank] withdraw $account $amount $text
    $self: set content [expr [$self: set content] - $amount]
    puts "Thank you - come again"
 }

Now let's try out the thing with a couple of use cases. We are founding a bank, which has one ATM and soon acquires two customers, a company, and a private person (who happens to work at that company). New customers are registered, and each opens one account, and deposits some initial money. Later, PHB Inc. pays wages via bank transfer, and John Brown goes to the ATM to get some cash, and finally statements are printed for all (two) accounts:

 Bank b  name "1st National Piggy Bank" balance 100000.00
 set atm [ATM #auto bank b location "1 Main St/1600 Elm St"]
 b: lappend atms $atm
 $atm refill 1500.00

 set c [b newCustomer [Customer #auto name "PHB, Inc."]]
 set a [b newAccount $c]
 b deposit $a 50000.00

 set c2 [b newCustomer [Customer #auto name "John Brown"]]
 set a2 [b newAccount $c2]
 b deposit $a2 500.00
 b transfer $a $a2 3500.00 "October wages/JOHN BROWN"
 $atm withdraw $a2 120.00
 
 foreach account [b: set accounts] {
     puts [b statement $account]
 }

The final "printing" of account statements should look like this:

 1st National Piggy Bank * Statement for Account 626880
 Customer: PHB, Inc.
 Starting balance:                            0.00
 transfer from cash                       50000.00
 October wages/JOHN BROWN                 -3500.00

 Current balance:                         46500.00

 1st National Piggy Bank * Statement for Account 626894
 Customer: John Brown
 Starting balance:                            0.00
 transfer from cash                         500.00
 October wages/JOHN BROWN                  3500.00
 ATM#116 at 1 Main St/1600 Elm St          -120.00

 Current balance:                          3880.00

except for the account numbers... Still a far cry from real banking software, but it was a nice little Sunday evening project ;-)