Un pattern per "atomicamente" aggiorna una coppia di oggetti

2

C'è un modello standard in OOP per tipo di "atomicamente" aggiornare una coppia di oggetti, come una coppia di conti bancari su una transazione?

Mi piacerebbe avere in qualche modo un singolo metodo pubblico esposto che faccia tutto in una volta, ma può solo venire fuori con modi di farlo facendo due chiamate di metodi pubblici per aggiornare gli oggetti separatamente.

Attualmente sto sperimentando con Racket , ma apprezzerei gli esempi in qualsiasi lingua o una descrizione di un pattern standard.

Ecco un esempio di ciò che ho sperimentato in Ruby (con il quale sono più a mio agio che con Racket ). Non mi interessa il blocco qui, solo con l'interfaccia di classe.

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 }"

Funziona usando protected invece di private .

    
posta Alexey 27.01.2014 - 07:31
fonte

3 risposte

7

È possibile astrarre la transazione come un oggetto in sé, che quindi fornisce un'unica interfaccia per gestire la transazione e i suoi partecipanti. Prima proprietà delle transazioni, Atomicity soddisfa le tue esigenze di atomicità / punto singolo.

vale a dire. qualcosa di simile:

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
            }
        }
    }

La parte importante è, devi utilizzare i rispettivi blocchi per ogni accesso a questi tre oggetti perché funzioni.

    
risposta data 27.01.2014 - 08:24
fonte
1

Invece di un metodo public addAmount(amount) , il tuo class Account potrebbe avere un metodo public addAmountFrom(otherAccount, amount) che controlla automaticamente l'altro account ed esegue la controperatività su di esso.

Quando ogni cambio di saldo di un account deve specificare un altro account, applica automaticamente anche la regola "nessuna voce senza controindicazione" della contabilità a partita doppia.

La controperazione può essere eseguita attraverso un private o protected metodo subtractAmountTo(this, amount) . La maggior parte delle lingue OOP consente di chiamare metodi privati e protetti da oggetti della stessa classe. Dovrebbe essere privato perché il metodo presuppone che la contro-entrata sia eliminata dal chiamante.

    
risposta data 27.01.2014 - 09:37
fonte
0

Penso di aver trovato una soluzione soddisfacente anch'io. Ecco una versione Ruby :

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"

Non mi interessa il blocco, ma può essere implementato facilmente all'interno di TransactionService#transfer .

    
risposta data 12.02.2014 - 14:47
fonte

Leggi altre domande sui tag