Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- >Object-Oriented Design is about the messages that get sent between objects and not the objects themselves.
- Message passing is usually referred to the form of communication between parallel processes, but in a broader sense, it is how object interfaces communicate in dynamically typed languages. As in Ruby, thinking of 'method call' in terms of 'message passing' adds perspective to how you can design delegations.
- ---
- Russ Olsen, in Design Patterns in Ruby reiterates thinking about object interfaces and the combination of composition and delegation as a powerful technique to design flexible applications. He gives us a perspective that method invocation between object interfaces can be thought as 'message passing'.
- For example, take a look at the code below.
- `object.do_something(arg)`
- - In statically typed languages, methods are generally considered to be more 'baked' into the objects, in the sense that running the code above would translate to the object calling or invoking the #do_something method found within itself.
- - In dynamically typed languages, it is possible for the object to not contain a method that was invoked (statically typed languages are compiled). We can translate the code above to 'sending or passing the #do_something message to the object'.
- Ruby will initially look for the method first in the class, then in its superclass, and so on, until it either finds the method or runs out of superclass (if it doesn't find one, a fallback method is called: method_missing). The idea of 'message passing' is thoroughly integrated in the language. Thus you can not only catch unexpected messages, but also send messages (delegate) across objects with `object.send()`.
- For example:
- `:message [payload1, ... payloadN]`
- is a Ruby message consists of a name, usually represented by a symbol such as :message, and an optional payload. The payload is often called the list of arguments or parameters.
- ```
- 5.send :+, 2
- 5.+ 2 # same thing
- 5 + 2 # same thing
- ```
- You can use the send method on any object to invoke a message handler. These 'send' methods are definitely easier to understand in the context of 'message passing'.
- ### Implementations
- There are a few implementations.
- When building applications thinking about object interfaces, you end up with a lot of boilerplate code, only of dumb methods that only exist for delegation. These can become a little more elegant with the method_missing fallback mentioned above.
- Example use of method_missing:
- ```test.rb
- class TestMethodMissing
- def hello
- puts 'hello'
- end
- def method_missing(name, *args)
- puts "unknown method called: #{name}"
- puts "arguments: #{args.join(' ')}"
- end
- end
- ```
- ```
- test = TestMethodMissing.new
- test.hello
- hello
- test.goodbye('cruel', 'world')
- unknown method called: goodbye
- arguments: cruel world
- test.send(:hello)
- hello
- test.send(:goodbye)
- unknown method called: goodbye
- arguments: cruel world
- ```
- Imagine a bank account application with the need of proxy to control access to the subject. It uses protective/virtual proxy around the real_account.
- ```account.rb
- class BankAccount
- attr_reader :balance
- def initialize(starting_balance = 0)
- @balance = starting_balance
- end
- def deposit(amount)
- @balance += amount
- end
- def withdraw(amount)
- @balance -= amount
- end
- end
- class AccountProtectionProxy
- def initialize(real_account, owner_name)
- @subject = real_account
- @owner_name = owner_name
- end
- def deposit(amount)
- check_access
- @subject.deposit(amount)
- end
- def withdraw(amount)
- check_access
- @subject.withdraw(amount)
- end
- def balance
- check_access
- @subject.balance
- end
- # ad infinitum ...
- def check_access
- if Etc.getlogin != @owner_name
- raise "Illegal access: #{Etc.getlogin} cannot access account."
- end
- end
- end
- ```
- The code above can be refactored to this:
- ```proxy.rb
- class AccountProtectionProxy
- def initialize(real_account)
- @subject = real_account
- @owner_name = owner_name
- end
- def method_missing(name, *args)
- check_access
- @subject.send(name, *args)
- end
- def check_access
- login_name = Etc.getlogin
- if login_name != @owner_name
- raise "Illegal access: #{login_name} cannot access account."
- end
- end
- end
- ```
- There is also the `Forwardable` module to generate similar dull delegation methods.
- ```forwardable.rb
- require 'forwardable'
- class Delegator
- extend forwardable
- def_delegators :@base, :method_b, :method_c
- def initialize(base)
- @base = base
- end
- def method_a
- @base.method_a
- end
- end
- ```
- ### Concerns
- Remember when using the method_missing technique, that every object actually starts out with a minimal set of methods - those that it inherits from Object class. For example, be careful of `to_s` as every object inherits a method called `to_s` from Object.
- Delegation in general, and using method_missing may worsen performance.
- Overuse leads to obscure code.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement