Is there a standard pattern in OOP to kind of “atomically” update a pair of objects, like a pair of bank accounts on a transaction?
I would like to have somehow a single public method exposed that does it all at once, but can only come up with ways of doing it by making two calls of public methods to update objects separately.
I am currently experimenting with Racket, but i would appreciate examples in any language, or a description of a standard pattern.
Here is an example of what i have experimented in Ruby (with which i am more comfortable than with Racket). I am not concerned with locking here, just with class interface.
class Account
def initialize(total)
@total = total
end
def total
@total
end
def transfer_to(another, sum:)
change_total_by(-sum)
another.change_total_by(sum)
end
protected
def change_total_by(sum)
@total += sum
end
end
a = Account.new(10)
b = Account.new(20)
a.transfer_to(b, sum: 5)
print "New a.total = #{ a.total },nnew b.total = #{ b.total }"
This works using protected
instead of private
.
3
You can abstract the transaction as an object in itself, which then provides a single interface to manage the transaction and its participants. First property of transactions, Atomicity satisfies your requirement of atomicity/single point.
I.e. something like this:
public class Transaction {
...
private Account _source;
private Account _destination;
public void Commit() {
lock( _globalAccountWriteLock )
lock( _source )
lock( _destination )
lock( this ) {
try {
UpdateSource( _source );
UpdateDestiantion( _destination );
}
catch( Exception e ) {
// Rollback
}
}
}
The important part is, you have to use respective locks for every access to these three objects for it to work.
10
Instead of a public addAmount(amount)
method, your class Account
could have a public addAmountFrom(otherAccount, amount)
method which automatically checks the other account and performs the counter-operation on it.
When each balance-change of a account must specify another account, you also automatically enforce the “no entry without counter-entry” rule of double-entry bookkeeping.
The counter-operation could be done through a private
or protected
method subtractAmountTo(this, amount)
. Most OOP languages allow private and protected methods to be called from objects of the same class. It should be private because the method assumes that the counter-entry is taken care off by the caller.
2
I think i’ve found a satisfactory solution myself. Here is a Ruby version:
class TransactionService
def initialize
@accounts = {}
end
def register_account(account_id, deposit_proc:, withdrawal_proc:)
@accounts.store(account_id,
:deposit_proc => deposit_proc,
:withdrawal_proc => withdrawal_proc)
end
def forget_account(account_id)
@accounts.delete(account_id)
end
def transfer(sender_id:, receiver_id:, sum:)
@accounts[sender_id ][:withdrawal_proc][sum]
@accounts[receiver_id][:deposit_proc ][sum]
end
end
class Account
def initialize(total, transaction_service:)
@total = total
transaction_service.register_account(self.public_id,
deposit_proc: proc do |sum| change_total_by(sum) end,
withdrawal_proc: proc do |sum| change_total_by(-sum) end)
end
def total
@total
end
def public_id
self.object_id # just for simplicity
end
private
def change_total_by(sum)
@total += sum
end
end
ts = TransactionService.new
a = Account.new(10, transaction_service: ts)
b = Account.new(20, transaction_service: ts)
print "Initial a.total = #{ a.total },n"
"initial b.total = #{ b.total }.n"
"---n"
ts.transfer(sender_id: a.public_id,
receiver_id: b.public_id,
sum: 5)
print "New a.total = #{ a.total },n"
"new b.total = #{ b.total }.n"
I do not care about locking, but it can be implemented easily inside TransactionService#transfer
.
2