Beyond Java - Bruce Tate [59]
Look at this contrived little example. To build the friendliest possible application, you may want to build a mixin to greet any object by name. You'd code it like this:
irb(main):021:0> module Greetable
irb(main):022:1> def greet
irb(main):023:2> puts "Hello, " + self.name
irb(main):024:2> end
irb(main):025:1> end
=> nil
Then, you can include this code in a class called Person:
irb(main):011:0> class Person
irb(main):012:1> include Greetable
irb(main):013:1> def initialize(name, age)
irb(main):014:2> @name=name
irb(main):015:2> @age=age
irb(main):016:2> end
irb(main):017:1> attr_reader :name
irb(main):018:1> end
=> nil
You can use this code in Person:
irb(main):039:0> person=Person.new("Bruce",40)
=> # irb(main):040:0> person.greet Hello, Bruce => nil While mixins seem interesting, this code probably smells wrong to you. Unless you could better integrate the Person methods in the mixin, it's just a recipe to make bad design decisions: you can include stuff that doesn't really have anything to do with Person into Person. But it's more powerful than that. You can separate an aspect, or a capability, into a mixin. What makes mixins so powerful is this: you can also access Person's class methods in your module. In fact, we used Person.name, in the module, before we had even defined Person. If it sounds confusing, just look at the following module. inspect is a class method that puts the contents of an object in string form: irb(main):147:0> module Reversible irb(main):148:1> def inspect irb(main):149:2> super.reverse irb(main):150:2> end irb(main):151:1> end => nil Note that you haven't defined a class yet, but you're still using the inspect class method. That may seem strange until you include the module in the Calculator class that we made before: irb(main):152:0> class Person irb(main):153:1> include Reversible irb(main):154:1> end => Person Now you've included the module, and it has a class. It's now a mixin. You can call any new instance methods that it defines. It will assume the class that you add it to. Look at what happens when you instantiate it: irb(main):155:0> p=Person.new("Bruce", 40) => >"ecurB"=eman@ ,04=ega@ 0711c82x0:nosreP<# irb actually calls inspect when you instantiate an object. Did you see the garbled line at the bottom? It's actually "Person:0x28c1170 @age=40, @name=\"Bruce\" in reverse. That's impressive. Now, you can add a mixin that can inspect the class, and integrate the most intimate details of the class into the mixin. And you can do all of this integration before a class even exists. I can use mixins for things like security or persistence. Java developers often resort to AOP to get the capability of mixins. Interceptors For example, let's say that my friend, Dave Thomas, asks me to watch his laptop for a few minutes before his big Ruby presentation. I could go to his Ruby shell and enter this little gem based on an example from his book, Figure 6-2. In Ruby, to do method interception, you simply rename and replace a method, with the new implementation calling the old Programming Ruby (Pragmatic Bookshelf). This version intercepts new, as you can see in Figure 6-2. I simply rename the original and call it from the replacement new. The interceptor will print out a message whenever Ruby creates a new object. Here's how easy it is: class Class alias_method :original_new, :new def new(*args) result = original_new(*args) print "Unattended laptop error. " return result end end And when Dave gets back to teach
I've said that Java framework developers these days place an ever-increasing value on techniques that change the behavior of an existing class, without changing its code. One such technique is method interception . JBoss and Spring use method interception to attach arbitrary services to a POJO. With Ruby, interception is easy. You simply take a method, rename it, and put another method in its place (see Figure 6-2).