The last few years I’ve been rather active in the DCI community but never really blogged much about it. This post is not going to be my first post on the main DCI concepts either. I’ve been fairly active on the mailing list object-composition, on stackoverflow.com and not least developing the Marvin compiler. One should think with all that activity I’d have enough information to a series of blog post and potentially an entire book and one would probably be right. However this time around I’ll will stick to describing the use of a Ruby tool I’ve recently created.
The first step to creating this tools was learning Ruby altogether. Until recently I’d only read Ruby but never actually coded anything in Ruby nor read a text on the subject and I couldn’t find a better “hello world” that trying to replicate functionality I’d already coded in a different language.
Inspired by a discussion on SO, where you could be led to believe that DCI in Ruby requires using #extend and that it will always be slow I decided to see how many of the tricks used in the Marvin compiler to change C# into Marvin I could replicate in Ruby. From a functional point of view I’m satisfied with the result
The canonical DCI example (MoneyTransfer) looks like this
class MoneyTransfer < Context #Create a role called source with two role methods 'withdraw' and 'log' role :source do role_method :withdraw do |amount| source.movement(amount) source.log "withdrawal #{amount}" end role_method :log do |message| p "role #{message}" end end #Create a role called source with two role methods 'deposit' role :destination do role_method :deposit do |amount| destination.movement(amount)
destination.log "deposit #{amount}" end end interaction :transfer do |amount| source.withdraw -amount destination.deposit amount end def initialize(s,d) self.source = s self.destination = d end end There's three methods in play here to actually generate the code
- role -> defines a role, the accessor to the current RolePlayer is defined as private
- role_method -> defines a role method, though it’s defined as a block, that block is never really executed. Instead it’s transformed into an instance method of the context class
- interaction -> defines a public instance method. The only “magic” here has to do with bookkeeping in relation to the context stack
There’s absolutely no use of #extend. However there’s is some dynamic changes to classes but these are only performed the first time an object of a given type is playing any role and in the odd case where a role method and an instance method clashes then this is handled on a class level as well.
Being a Ruby novice there might be some subtleties I’ve missed or even big no-nos 🙂 however for the MoneyTransfer I’ve verified that
- An instance method is chosen even if the object is playing two roles in the same context and the ‘other’ role defines a method with the same name (in the example above log is both a role method for source and on the account object I used for testing)
- A role method is chosen over an instance method if the current role defines a method that clashes with an instance method
- The role behavior doesn’t leak. I.e. the object looses the capabilities as soon as it stops playing the role (or when accessed outside the role context (pun not intended)
At present the code can be found as a gist