Applying Some StructureBoth Ruby and Java are object-oriented languages. Both support object models with single inheritance. Still, you're going to see some differences between Ruby and Java: Figure 6-1. Java programmers refactor the inside of a loop; code blocks let Ruby developers refactor the outside of a loop, too
For the most part, you can still use your OO design skills in Ruby as you did in Java. You'll also see some common design patterns, like model-view-controller.
ClassesRuby is object-oriented. I've shown you how to use Ruby objects , but not yet how to create one. Let's make a class called Calculator. Create a file called calculator.rb that looks like this:
class Calculator
def initialize
@total=0
end
def add(x)
@total += x
end
def subtract(x)
@total -= x
end
end
You've declared three methods. Ruby will call initialize when it creates a new object, such as this calculator. Notice that initialize defines an instance variable called @total. In Ruby, instance variables start with @, class variables start with @@, and global variable start with $. Now, in irb, you can load the file and use the calculator.
irb(main):005:0> require 'Calculator'
=> true
irb(main):006:0> c=Calculator.new
=> #<Calculator:0x28b4a98 @total=0>
irb(main):007:0> c.add 100
=> 100
irb(main):008:0> c.subtract 40
=> 60
And it works, just like you'd expect. Ruby developers take advantage of open classes . I'm going to change the definition of Calculator, but keep in mind that we still have c, an instance of Calculator. I actually open up the definition of the class again like this:
irb(main):009:0> class Calculator
irb(main):010:1> def reset
irb(main):011:2> @total = 0
irb(main):012:2> end
irb(main):013:1> end
I just added a method called reset. I also could have changed an existing method.
irb(main):014:0> c.reset
=> 0
That's amazing. I changed the class definition of an existing class. That's a useful capability for debugging, iterative programming, and metaprogramming. Ruby also lets you subclass. To subclass, you use the < operator:
irb(main):015:0> class IrsCalculator < Calculator
irb(main):016:1> def add(x)
irb(main):017:2> x = x / 2 if x>0
irb(main):018:2> super
irb(main):019:2> end
irb(main):020:1> end
=> nil
You can use it, and IrsCalculator will take a little off the top for you:
irb(main):027:0> c=IrsCalculator.new
=> #<IrsCalculator:0x28b6b80 @total=0>
irb(main):028:0> c.add 100
=> 50
These concepts should look familiar to you. Classes package instance data and methods together. An instance of a class is an object. All classes have single parents, and eventually inherit from Object, with the exception of Object:
irb(main):031:0> Class.superclass
=> Module
irb(main):032:0> Module.superclass
=> Object
irb(main):033:0> Object.superclass
=> nil
Using MixinsTo implement a mixin, Ruby uses a concept called a module. A module lets you group together methods and classes. You can't instantiate a module, and a module doesn't stand alone. A module isn't a class, but it does have its own namespace. Modules form the foundation of classes and mixins . Mixins are not new. Smalltalk supported them back in 1971. Recall that a mixin is an interface with an implementation. That means you can group together a set of methods that many classes may need to use. 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)
=> #<Person:0x2a970a0 @age=40, @name="Bruce">
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. InterceptorsI'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). 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 his class, he'll get a nice surprise when he does anything that creates an object (which is pretty much anything in Ruby):
irb(main):009:0> i=[1,2,3]
Unattended laptop error. Unattended laptop error. Unattended laptop error.
Unattended laptop error. Unattended laptop error. Unattended laptop error.
Unattended laptop error. Unattended laptop error. Unattended laptop error.
Unattended laptop error. Irb(main):010:0>
That's an interceptor in eight lines of code. You get extra credit if you know which 10 objects get created. You don't have any Java proxies, code generation, or aspect-oriented programming. Of course, you'll not want to try this with the real Dave. That would be like throwing a firecracker under Albert Einstein's car. Like Albert and the atom, you don't want to unleash this kind of power without knowing where all the energy is going to go. AOPJava developers depend on AOP with increasing frequency. AOP lets you add services to your POJO without modifying any code. AOP helps you control the flow of your application, such as adding custom methods at interesting pointsfor instance, before or after a method executes. In particular, you'll often see AOP for:
Of course, AOP is a much broader tool, and if it is successful, the typical use cases obviously will grow in scope and power. Right now, though, Java developers most frequently use the power of AOP through frameworks like Spring. You can look at interceptors as a more primitive tool to accomplish the same sorts of things. The JBoss framework and containers like HiveMind use interceptors to provide a wide range of services, like transactions. For Ruby developers, AOP is not quite as urgent, because you've already got robust tools to deal with these kinds of concerns:
In short, Ruby can already solve many AOP-like problems without AOP, and will add AOP-like features in the very near future. Some Ruby programmers are concerned that AOP code may be more difficult to maintain. The core value of AOP that's not yet supported in Ruby is the ability to specify a point cut quickly and efficiently, which lets you use regular expressions to define interceptors wherever you need them. Ruby already has the core features that should make point cuts easy to implement:
Given these capabilities, AOP becomes a very lightweight feature. Right now, Ruby developers prefer to implement AOP-like features, piecemeal, in a style that best fits the architecture. Dependency InjectionThe difference dependency injection in Java and Ruby is a little tougher to understand for Java developers. In Java, dependency injection is rapidly changing the way that we build applications. It's a relatively simple concept:
class Speaker {
void speak(String words) {
System.out.println(words);
}
}
class Consumer {
Speaker mySpeaker;
void saySomething( ) {
mySpeaker.speak("something");
}
}
Notice Consumer. It doesn't instantiate Speaker. That job goes to a third party. We'll call it Container:
class Container {
public static void main(String[ ] args) {
Speaker speaker=new Speaker( );
Consumer consumer=new Consumer( );
consumer.mySpeaker = speaker;
consumer.saySomething( );
}
}
You can make some simple improvements. You can encapsulate mySpeaker with a getter and setter. You can then extract an interface called Speaker, and provide implementations for FrenchSpeaker, EnglishSpeaker, and SpanishSpeaker. You can also make a configuration file, in Java or XML, describing all the objects that you want to treat this way. You'd then have most of what you'd need for a basic dependency injection container: configuration, third-party life cycle control, and the ability to loosen the coupling between Speaker and Consumer. With a dependency injection container, you could change implementations of Speaker without changing any code in any of the consumers. You could also inject a test implementation of Speaker without impacting the base code, a critical technique in Java for test-first development. You'd also have a consistent strategy for configuration. A few things come up right off the bat when you look at dependency injection in Ruby. First, Java's not very good at configuration, but Ruby lets you represent structured data quite well, often with far less invasive syntax than XML. You also can solve many of the coupling problems by changing the definition of a class on the fly. It's easier, for example, to inject those mock objects into hard-to-reach places. Some developers in Ruby seem to think dependency injection is important and that the idea will have a place in the Ruby mainstream, given time. It should come as no surprise to you that Ruby has an outstanding dependency injection framework called Needles. Others tend to think that dependency injection should happen in spots, instead of with a single, general-purpose framework. Since it's easy to change a class definition on the fly, you can easily inject the behavior that you need without adding another layer of complexity, across the application. Most of the Ruby programming community seems to be converging on the idea that Ruby's overall dynamic design makes dependency injection unnecessary for all but the most complex applications (see the sidebar, "Jim Weirich, Jamis Buck, and David Heinemeier Hansson, Three Ruby Experts: Does Ruby Need Dependency Injection?").
|


