On Feb 9, 11:40 am, Brian Candler <b.cand... / pobox.com> wrote:
> Thomas Sawyer wrote:
> >  http://architects.dzone.com/videos/dci-architecture-trygve
>
> Interesting.
>
> Part 2 explores some ideas in Ruby with a simple bank transfer example.
> What I don't get is why you'd want to inject the logic of transferring
> money between two accounts into one of the accounts and then call it
> there. Surely you could just do it all in the context object itself? In
> that case, the transfer-money context would just become what I'd call a
> 'controller'.
>
> So I'd find it useful to see a more extensive example which shows the
> benefits of working this way.

I just finished watching the 2nd video. I agree with you. Coplien does
an awful job of explaining things. Trygve, despite his age, does a
much better job.

> Then at the end, it says that an account isn't really an object at all -
> but all the previous code has shown it as a concrete object (e.g.
> Account.find(id)). So an example of what an account role *should* look
> like in code would be good.

I don't know what he is talking about. It's as if he thinks, if
something isn't solid it isn't an object. And his whole speel about
logging-in is not a usecase because there's no business goal, is silly
too. He's splitting hairs over words and as much as he thinks DCI is
so cool, I'm not sure he actually "gets it" himself. However, at the
very beginning he does point out the main point of the whole pursuit
-- code readability.

His Ruby code, btw, wasn't very well written, would not run and worse,
I don't think represents DCI well either. So I threw together a fix
that I think represents it at least a little better. Still a simple
bank transfer, but it works, so that in it's self is an
improvement ;)

One thing I would point out, Coplien's TransferMoneyContext is a
Command pattern --a class that encapsulates a single action. I don't
think it's necessary to go that far. While my example follows his, if
I were doing it otherwise, I would probably make it an
AccountInteractions class and define methods within it for all the
ways in which two accounts could interact.

  #
  class Account
    # simple account db
    def self.accounts
      @@accounts ||= {}
     end

    def self.find(accountID)
      accounts[accountID]
    end

    attr :accountID
    attr :balance

    def initialize(accountID, initialBalance)
      Account.accounts[accountID] = self

      @accountID = accountID
      @balance = initialBalance
    end
  end

  #
  class SavingsAccount < Account
    def initialize(accountID, initialBalance)
      super(accountID, initialBalance)
    end

    def availableBalance; @balance; end
    def decreaseBalance(amount); @balance -= amount; end
    def increaseBalance(amount); @balance += amount; end

    def updateLog(message, time, amount)
      puts "%s %s #%s $%.2f" % [message, time, accountID, amount.to_f]
    end
  end

  # Use Case (Context)
  class MoneyTransfer
    attr :amount
    attr :source_account
    attr :destination_account

    def initialize(amt, sourceID, destID)
      @amount = amt
      @source_account = Account.find(sourceID)
      @destination_account = Account.find(destID)
    end

    def execute
      source_account.extend TransferSource
      destination_account.extend TransferDestination

      source_account.withdraw(amount)
      destination_account.deposit(amount)

      #source_account.unextend TransferSource
      #destination_account.unextend TransferDestination
    end
  end

  # Account Role
  module TransferSource
    def withdraw(amount)
      raise "Insufficiant Funds" if balance < amount
      decreaseBalance(amount)
      updateLog "Transfer Out", Time.now, amount
    end
  end

  # Account Role
  module TransferDestination
    def deposit(amount)
      increaseBalance(amount)
      updateLog "Transfer In", Time.now, amount
    end
  end

  # try it out

  SavingsAccount.new(1, 500)
  SavingsAccount.new(2, 100)

  transfer_case = MoneyTransfer.new(50, 1, 2)
  transfer_case.execute


Notice the remarked #unextend lines. For a real implementation of DCI,
we would want to remove these roles once we used them, but Ruby's
extend doesn't allow that, of course.

So the bottom line I think is this. You work out usecases (i.e.
contexts) for actually doing things. You make your objects pretty dumb
--primarily state bags. You figure out the roles your objects must
play to satisfy those use cases and code those. Then you code the
usecases with the roles and objects so as to get the job done. The
whole programs then becomes easier to read b/c you are reading
usecases first,  which explains things as the interaction of roles
played by simple objects. And presto the "Code is Chunkable".

(P.S. I also think this is much more like AOP then Coplien is willing
to admit.)