#{line}
" ) end puts(' ') puts('') end end Clearly, we are taking some liberties with this code in the interest of keeping this example simple. In real life, our report would not just be hard-coded into the class, and we certainly would not just include arbitrary text into an HTML file without checking for the odd “<” or “>”. That said, the preceding code has some good things going for it. It is simple, it is easy to use, and it does produce HTML: report = Report.new report.output_report If all you want to do is generate some basic HTML, this code or something like it is all you really need. Keeping Up with What Life Throws at You Unfortunately, even when life starts out simple, it rarely stays that way. Just months after you finished the preceding programming masterpiece, you get a new require- ment: Your formatting object needs to produce plain text reports along with the cur- rent HTML. Oh, and we will probably need PostScript and maybe RTF output before the year is out. Sometimes the simplest solutions are the best, so you just code your way around the problem in the dumbest possible way: class Report def initialize @title = 'Monthly Report' @text = ['Things are going', 'really, really well.'] end 60 Chapter 3. Varying the Algorithm with the Template Method def output_report(format) if format == :plain puts("*** #{@title} ***") elsif format == :html puts('') puts(' ') puts("#{line}
" ) end end if format == :html puts(' ') puts('') end end end Yuk. This second version may work, but it is a mess. The code to handle the plain text formatting is tangled up with the HTML code. Worse, as you add more formats (remember that looming requirement for PostScript!), you will have to go back and rework the Report class to accommodate each new format. The way the code stands right now, each time you add a new format you risk breaking the code for the other formats. In short, our first attempt to add a new output format violates one of the guiding principles for design patterns: It mixes up code that is changing with code that is not changing. Separate the Things That Stay the Same The way out of this quandary is to refactor this mess into a design that separates the code for the various formats. The key in doing so is to realize that no matter which Separate the Things That Stay the Same 61 format is involved—whether plain text or HTML or the future PostScript—the basic flow of Report remains the same: 1. Output any header information required by the specific format. 2. Output the title. 3. Output each line of the actual report. 4. Output any trailing stuff required by the format. With this sequence in mind, we can reach back to the lessons we learned in Object-Oriented Programming 101: Define an abstract base class with a master method that performs the basic steps listed above, but that leaves the details of each step to a subclass. With this approach, we have one subclass for each output format. Here is our new, abstract Report class: class Report def initialize @title = 'Monthly Report' @text = ['Things are going', 'really, really well.'] end def output_report output_start output_head output_body_start output_body output_body_end output_end end def output_body @text.each do |line| output_line(line) end end def output_start raise 'Called abstract method: output_start' end def output_head raise 'Called abstract method: output_head' end 62 Chapter 3. Varying the Algorithm with the Template Method def output_body_start raise 'Called abstract method: output_body_start' end def output_line(line) raise 'Called abstract method: output_line' end def output_body_end raise 'Called abstract method: output_body_end' end def output_end raise 'Called abstract method: output_end' end end Of course, this new Report class is not really an abstract class. While we might talk in theory about abstract methods and classes, the fact is that Ruby supports nei- ther. The ideas of abstract methods and classes do not really fit with Ruby’s easygoing, dynamic view of life. The closest we can come is to raise exceptions should anyone try to call one of our “abstract” methods. With our new Report implementation in hand, we can now define a Report subclass for each of our two formats. Here is the HTML class: class HTMLReport < Report def output_start puts('') end def output_head puts(' ') puts("#{line}
") end Separate the Things That Stay the Same 63 def output_body_end puts('') end def output_end puts('') end end Here is the plain text version: class PlainTextReport < Report def output_start end def output_head puts("**** #{@title} ****") puts end def output_body_start end def output_line(line) puts(line) end def output_body_end end def output_end end end Using our new report classes is straightforward: report = HTMLReport.new report.output_report report = PlainTextReport.new report.output_report Picking a format is now as easy as selecting the right formatting class. 64 Chapter 3. Varying the Algorithm with the Template Method Discovering the Template Method Pattern Congratulations! You have just rediscovered what is probably the simplest of the orig- inal GoF patterns, the Template Method pattern. As shown in Figure 3-1, the general idea of the Template Method pattern is to build an abstract base class with a skeletal method. This skeletal method (also called a template method) drives the bit of the processing that needs to vary, but it does so by making calls to abstract methods, which are then supplied by the concrete subclasses. We pick the variation that we want by selecting one of those concrete subclasses. In our example, the basic outline is all the things you need to do to generate a report: output any header information, the report title, and then each line of the report. In this case, the detail-supplying methods of the subclasses deal with writing out the report in the correct format, either plain text or HTML. If we engineer all of these tasks correctly, we will end up separating the stuff that stays the same (the basic algorithm expressed in the template method) from the stuff that changes (the details supplied by the subclasses). One characteristic that the HTMLReport and PlainTextReport classes share with all properly written Template Method pattern concrete subclasses is that they look fragmentary. Like the good concrete subclasses that they are, both HTMLReport and PlainTextReport override output_line and the other abstract methods. The subclasses get their fragmentary appearance from the fact that they do not override the key template method, output_report. In the Template Method pattern, the abstract base class controls the higher-level processing through the template method; the sub- classes simply fill in the details. Discovering the Template Method Pattern 65 Figure 3-1 Class Diagram for the Template Method pattern def template_method operation1() operation2() operation3() end ConcreteClass2 operation1() operation2() operation3() ConcreteClass1 operation1() operation2() operation3() AbstractClass template_method() operation1() operation2() operation3() Hook Methods If you go back and look at PlainTextReport, you will see that while it does override the output_start and output_end methods as well as the start and end methods for the body, there is no actual code in any of the PlainTextReport versions of these methods. This is reasonable enough: Unlike an HTML document, a plain text document does not need any leading or trailing formatting. But there really is no reason to force a class such as PlainTextReport, which has no use for all of these start and stopmethods, to define them anyway. It makes more sense for the base Report class to simply supply a default implementation of these methods for the convenience of its subclasses: class Report def initialize @title = 'Monthly Report' @text = ['Things are going', 'really, really well.'] end def output_report output_start output_head @text.each do |line| output_line(line) end output_end end def output_start end def output_head raise 'Called abstract method: output_head' end def output_body_start end def output_line(line) raise 'Called abstract method: output_line' end def output_body_end end 66 Chapter 3. Varying the Algorithm with the Template Method def output_end end end Non-abstract methods that can be overridden in the concrete classes of the Template Method pattern are called hook methods. Hook methods permit the con- crete classes to choose (1) to override the base implementation and do something dif- ferent or (2) to simply accept the default implementation. Frequently, the base class will define hook methods solely to let the concrete subclass know what is going on. When the Report class calls output_start, for example, it is telling its subclasses, “We are ready to start outputting the report, so if you need to do something, do it now.” The default implementations of these informative hook methods are frequently empty. They exist merely to let the subclasses know what is happening but do not require the subclasses to override methods that do not interest them. Sometimes, however, the default implementation of a hook method may actually contain some code. In our Report example, we might default to treating the title like just another line of text: class Report def initialize @title = 'Monthly Report' @text = ['Things are going', 'really, really well.'] end def output_report output_start output_head @text.each do |line| output_line(line) end output_end end def output_start end def output_head output_line(@title) end def output_body_start end Hook Methods 67 def output_line(line) raise 'Called abstract method: output_line' end def output_body_end end def output_end end end But Where Are All the Declarations? Given that this chapter describes our first Ruby pattern, it is worth taking a moment to consider the issues of types and type safety in Ruby. If you are recently arrived from the world of statically typed languages, you may be wondering how our Report class and its subclasses can get away with the almost total lack of declarations. Nowhere in the Report class, you may have noticed, do we declare that @title is a string or that @text is an array of strings. In the same vein, when our client code creates a new HTMLReport,we never actually say that the variable formatter holds a reference to an instance of HTMLReport or Report—it just does: report = HTMLReport.new Ruby is dynamically typed, which means that the language does no checking to ensure that the objects being passed around have any particular class ancestry. The only thing that matters is that an object actually implements the methods that its clients want to call. In the preceding example, the Report class simply expects the @text object to behave like an array of strings. If @text looks like an array of strings—that is, if you can get the third string out of it with @text[2]—then what- ever its actual class, it is the correct type. This “I am what I am” approach to typing has been called duck typing. The name comes from the old bit of wisdom that goes, “If it looks like a duck and quacks like a duck, then it is a duck.” Another way to look at this issue is to think of static typing as working like an aristocracy: Statically typed languages are con- stantly asking about your parent or grandparent, or perhaps, in the case of Java-style interfaces, your aunts and uncles. In a statically typed language, an object’s family tree matters deeply. Dynamically typed languages, by contrast, are meritocracies: 68 Chapter 3. Varying the Algorithm with the Template Method They are concerned with which methods you have, rather than where those methods came from. Dynamically typed languages rarely ask about an object’s ancestry; instead, they simply say, “I don’t care who you are related to, Mac. All I want to know is what you can do.”1 Types, Safety, and Flexibility People who are used to programming in statically typed languages often wonder how all of this could possibly work. You might think that all of this free and easy duck typ- ing stuff will certainly lead to disaster, with programs constantly crashing as they try to format some HTML with a database connection or attempt to tell the number 42 to generate a monthly report. Surprisingly, it turns out that these kinds of outrageous typing problems rarely occur. You can find evidence of this robustness in the world of Java programs, of all places. Almost all Java programs written before the advent of Java 1.5—and that covers the bulk of existing Java programs—use the containers from the java.util package, things like HashMap and ArrayList. The pre-1.5 versions of these con- tainers provided no type safety at all, and even post-1.5 Java continues to provide non-type-safe versions of these containers for backward compatibility. Despite this cavalier attitude toward type safety, most Java programs do not mix up their socket objects with their Employee objects and crash while trying to give a network con- nection a pay raise. Statically typed languages are so pervasive these days that a key question is rarely asked: What is the cost of static typing? My answer is that static typing costs a lot. In fact, in the currency of programming effort and code clutter, static typing costs a for- tune. Look at a Java or C# program and count the number of tokens devoted to parameter and variable declarations. Add in most of the interfaces. Don’t forget those pesky class casts, where you convince the typing system that, yes, that really is a String over there. Add a bonus for each complex generic declaration. All of this code clutter is not free.2 Types, Safety, and Flexibility 69 1. And, in my mind at least, they say it with the accent of a New York cab driver. 2. In fairness, I must point out that static typing is very expensive in terms of code clutter as it is implemented in the languages in wide use today. There are, however, a number of much less widely used languages, such as OCaml and Scala, that manage to handle static typing with much less noise. And it is not just programming effort. There is a very real, albeit hidden cost to static typing: It tends to couple your system together much more tightly than neces- sary. Consider the following Java isEmpty() method: public boolean isEmpty(String s) { return s.length() == 0; } Now look at its Ruby twin: def empty?(s) s.length == 0 end On the surface, the two methods seem pretty much the same. Now consider that the Java version works only on arguments of type java.lang.String. The Ruby version will work on strings, to be sure—but it will also work with arrays, queues, sets, and hashes. In fact, the Ruby empty? method will work with any argument that has a length method. It doesn’t care what the exact type of the argument is, and perhaps it should not. The arguments for dynamic typing might sound counterintuitive to the statically typed ear. If you are used to static typing, in which you declare everything all the time, it might seem unrealistic to suppose that you can build large, reliable systems without strong type checking. But it is possible—and there are two very obvious examples that demonstrate just how possible it is. Ruby on Rails is by far the most prominent evidence that you can write reliable code in a dynamically typed language. Rails consists of tens of thousands of lines of dynamically typed Ruby code, and Rails is rock stable. If Rails does not persuade you, think about that other large lump of Ruby code in constant use everyday: the standard library that comes with Ruby. The Ruby standard library consists of more than 100,000 lines of Ruby. It is nearly impossible to write a Ruby program that does not use the Ruby standard library—and it works. Ruby on Rails and the Ruby standard library are the existence proof: You can write large bodies of reliable code in a dynamically typed language. The fear that dynamic typing will produce code chaos is mostly unfounded. Yes, type problems do occasionally cause Ruby programs to crash. But Ruby programs do not crash with any- thing like the frequency that you might expect given the lengths that statically typed languages go to in to avoid even the remote possibility of type errors. 70 Chapter 3. Varying the Algorithm with the Template Method Does this mean that dynamically typed languages are just better, and that we should give up on statically typed languages completely? The jury is still out on this question. As with most software engineering questions, the answer involves seeing the two options as a balance. Static typing is probably worth the price on large, complex systems built by huge teams. But dynamic typing has some significant advantages: Programs written in Ruby are generally a fraction of the size of their statically typed equivalents. And, as we saw with the empty? example and shall see in the chapters to come, dynamic typing offers a huge flexibility bonus. If all of this seems crazy to you, stick with me through the rest of this book, and give all of this dynamically typed insanity a chance. You may be pleasantly surprised. Unit Tests Are Not Optional One way that you can increase the chances that the surprise will be a pleasant one is to write unit tests. No matter whether you are writing in Java, C#, or Ruby, you should be writing unit tests: The second oldest3 joke in programming is “It compiles, so it must work.” While tests are important no matter which language you are using, they are crit- ical when you are working in a dynamic language such as Ruby. There is no compiler in Ruby to make that first sanity check—and perhaps give you a false sense of secu- rity. Instead, the only way to know that the program is working is to run some tests. The good news is that the same unit tests that you need to show that your code works will also tend to ferret out the vast bulk of those pesky type-related problems. The even better news is that if you know how to use JUnit, NUnit, or any of the other familiar XUnit-style libraries, then you already know how to write unit tests in Ruby. For example, the following class tests our Ruby empty? method: require 'test/unit' require 'empty' class EmptyTest < Test::Unit::TestCase def setup @empty_string = '' @one_char_string = 'X' @long_string = 'The rain in Spain' Unit Tests Are Not Optional 71 3. The oldest is that dead moth taped in the log book. @empty_array = [] @one_element_array = [1] @long_array = [1, 2, 3, 4, 5, 6] end def test_empty_on_strings assert empty?(@empty_string) assert ! empty?(@one_char_string) assert ! empty?(@long_string) end def test_empty_on_arrays assert empty?(@empty_array) assert ! empty?(@one_element_array) assert ! empty?(@long_array) end end True to its XUnit roots, Test::Unit runs each of the methods whose name starts with test as a test. If your test class has a setup method (as the preceding class does), it is run before each test method. If your class has a teardown method (and the pre- ceding class does not), it is run after each test method. Test::Unit comes equipped with a whole menagerie of assert methods. You can assert that something is true, or you can assert_equal that two objects are equal. If you want to be sure that you have something and not nothing, you can assert_not_nil. Running the unit tests could not be easier. If the test case above is found in a file called string_test.rb, then you can run the tests by simply executing the file as a Ruby program: $ ruby empty_test.rb Loaded suite empty_test Started .. Finished in 0.000708 seconds. 2 tests, 6 assertions, 0 failures, 0 errors Nothing like the feeling of a test that completes without complaint. 72 Chapter 3. Varying the Algorithm with the Template Method Using and Abusing the Template Method Pattern Given that it is possible to write a reliable implementation of the Template Method pat- tern in Ruby, how do you actually go about doing so? The best approach is an evolu- tionary one: Start with one variation and simply code as though it was the only problem that you need to solve. In our report example, you might have started with HTML: class Report def initialize @title = 'MonthlyReport' @text = ['Things are going', 'really, really well.'] end def output_report puts('') puts(' ') puts('#{line}
" ) end puts(' ') puts('') end end Here’s the formatter for plain text: class PlainTextFormatter < Formatter def output_report(title, text) puts("***** #{title} *****") text.each do |line| puts(line) end end end 78 Chapter 4. Replacing the Algorithm with the Strategy Now that we have completely removed the details of formatting the output from our Report class, that class becomes much simpler: class Report attr_reader :title, :text attr_accessor :formatter def initialize(formatter) @title = 'Monthly Report' @text = [ 'Things are going', 'really, really well.' ] @formatter = formatter end def output_report @formatter.output_report( @title, @text ) end end Using the new Report class is only slightly more complicated. We now need to supply Report with the correct formatting object: report = Report.new(HTMLFormatter.new) report.output_report The GoF call this “pull the algorithm out into a separate object” technique the Strategy pattern (Figure 4-1). The key idea underlying the Strategy pattern is to define a family of objects, the strategies, which all do the same thing—in our example, for- mat the report. Not only does each strategy object perform the same job, but all of the objects support exactly the same interface. In our example, both of the strategy objects Delegate, Delegate, and Delegate Again 79 Figure 4-1 The Strategy pattern Context Strategy operation() Strategy1 operation() Strategy2 operation() @strategy support the output_report method. Given that all of the strategy objects look alike from the outside, the user of the strategy—called the context class by the GoF—can treat the strategies like interchangeable parts. Thus, it does not matter which strategy you use, because they all look alike and they all perform the same function. But it does matter which strategy you pick, because each one does its thing a little differently. In the example, one of our formatting strategies produces HTML while the other produces plain text. If we were doing tax calculations, we might have use the Strategy pattern for state income tax calculations: one strategy to compute taxes for resi- dents of Virginia, and another to do the calculations according to the California tax code. The Strategy pattern has some real advantages. As we saw in the reportexample, we can achieve better separation of concerns by pulling out a set of strategies from a class. By using the Strategy pattern, we relieve the Report class of any responsibility for or knowledge of the report file format. In addition, because the Strategy pattern is based on composition and delegation, rather than on inheritance, it is easy to switch strategies at runtime. We simply swap out the strategy object: report = Report.new(HTMLFormatter.new) report.output_report report.formatter = PlainTextFormatter.new report.output_report The Strategy pattern does have one thing in common with the Template Method pattern: Both patterns allow us to concentrate the decision about which variation we are using in one or a few places. With the Template Method pattern, we make our decision when we pick our concrete subclass. In the Strategy pattern, we make our decision by selecting a strategy class at runtime. Sharing Data between the Context and the Strategy A real advantage of the Strategy pattern is that because the context and the strategy code are in different classes, a nice wall of data separation divides the two. The bad news is that we now need to figure a way to get the information that the context has but the strategy needs up and over that wall. We have essentially two choices here. First, we can continue with the approach that we have used so far—that is, pass in everything that the strategy needs as arguments when the context calls the 80 Chapter 4. Replacing the Algorithm with the Strategy methods on the strategy object. Recall that in our Report example, the report object passed in everything that the formatter needed to know in the arguments to output_report. Passing in all of the data has the advantage of keeping the context and the strategy objects crisply separated. The strategies have their interface; the con- text simply uses that interface. The downside of doing things this way is that if there is a lot of complex data to pass between the context and the strategy, then, well, you are going to be passing a lot of complex data around without any guarantee that it will get used. Second, we can get the data from the context to the strategy by having the con- text object pass a reference to itself to the strategy object. The strategy object can then call methods on the context to get at the data it needs. Returning to our reporting example, we might do something like this: class Report attr_reader :title, :text attr_accessor :formatter def initialize(formatter) @title = 'Monthly Report' @text = ['Things are going', 'really, really well.'] @formatter = formatter end def output_report @formatter.output_report(self) end end Here Report passes a reference to itself to the formatting strategy, and the for- matter class calls the new title and text methods to get the data it needs. Here is a refactored HTMLFormatter to go with this self-passing Report class: class Formatter def output_report(context) raise 'Abstract method called' end end Sharing Data between the Context and the Strategy 81 class HTMLFormatter < Formatter def output_report(context) puts('') puts(' ') puts("#{line}
") end puts(' ') puts('') end end Although this technique of passing the context to the strategy does simplify the flow of data, it also increases the coupling between the context and the strategy. This magnifies the danger that the context class and the strategy classes will get tangled up with each other. Duck Typing Yet Again The formatting example that we have built so far mirrors the GoF approach to the Strategy pattern. Our family of formatting strategies consists of the “abstract” Formatter base class with two subclasses, HTMLFormatter and PlainTextFormatter. This is, however, a very un-Ruby implementation, because the Formatter class does not actually do anything: It simply exists to define the common interface for all the formatter subclasses.There is certainly nothing wrong with this approach in the sense of it working or not working—it does work. Nevertheless, this kind of code runs counter to Ruby’s duck typing philosophy. The ducks would argue (quack?) that HtmlFormatter and PlainTextFormatter already share a common interface because both implement the output_report method. Thus there is no need to artificially grind in the point by creating what is essentially a do-nothing superclass. We can eliminate the Formatter base class with a few swipes of the delete key. We end up with the following code: 82 Chapter 4. Replacing the Algorithm with the Strategy class Report attr_reader :title, :text attr_accessor :formatter def initialize(formatter) @title = 'Monthly Report' @text = ['Things are going', 'really, really well.'] @formatter = formatter end def output_report() @formatter.output_report(self) end end class HTMLFormatter def output_report(context) puts('') puts(' ') # Output The rest of the report ... puts("#{line}
") end puts(' ') puts('') end end class PlainTextFormatter def output_report(context) puts("***** #{context.title} *****") context.text.each do |line| puts(line) end end end If you compare this code with the previous version, you will see that we eliminated the base Formatter class but not much else has changed. Thanks to dynamic typing, Duck Typing Yet Again 83 we still get the same reports proclaiming that all is well. Although both versions do work, the Ruby world would firmly vote for skipping the Formatter base class. Procs and Blocks It turns out that ripping out the base class is not the only way we can recast the Strategy pattern to give it a more Ruby-colored tint. But before we can take the next step, we need to explore one of the most interesting aspects of Ruby, code blocks and the Proc object. As users of object-oriented programming languages, we spend a lot of our time thinking about objects and how they go together. But there is an asymmetry regard- ing how we tend to think about our objects. We have no problem separating out data from an object—we can pull out the @text from a report and pass it around inde- pendently of the rest of the report. Yet we tend to think of our code as tightly bound, inseparable from the object to which it is attached. Of course, it doesn’t have to be this way. What if we could pull out chunks of code from our objects and pass those chunks around just like objects? It turns out that Ruby allows us to do exactly that. In Ruby, a Proc is an object that holds a chunk of code. The most common way to make a Proc is with the lambda method: hello = lambda do puts('Hello') puts('I am inside a proc') end Ruby calls the chunk of code between the do and the end a code block.1 The lambda method returns a new Proc object, a container for all of the code between the do and the end. Our hello variable points at the Proc object. We can run the code buried inside the Proc by calling the (what else?) call method. In our example, if we call the call method on the Proc object hello.call we will get Hello I am inside a proc 84 Chapter 4. Replacing the Algorithm with the Strategy 1. Other terms for the same concept are closure and lambda, which explains the name of our Proc-producing method. An extremely useful aspect of the Proc object is that it picks up the surrounding environment when it is created. Thus any variables that are visible when a Proc iscre- ated remain visible inside the Proc when it is run. For example, there is only one vari- able name in the following code fragment: name = 'John' proc = Proc.new do name = 'Mary' end proc.call puts(name) When we run this code, name will be set to “John” in the first statement, then reset to “Mary” when the Proc is called. Ultimately, Ruby will print “Mary”. If do and end seem like too much typing for you, Ruby provides a slightly more concise syntax using curly braces. Here is a quicker way to create our hello Proc: hello = lambda { puts('Hello, I am inside a proc') } Ruby programmers have adopted the convention of using do/end for multiple- line code blocks and braces for one liners.2 A more socially acceptable version of our example, then, is this: hello = lambda {puts('Hello, I am inside a proc')} Proc objects have a lot in common with methods. For example, not only are Proc objects and methods bundles of code, but both can also return a value. A Proc always returns the last value computed in the code block; thus, to return a value from Procs and Blocks 85 2. Actually, there is one real difference between do/end and the braces. In Ruby expressions, the braces have a higher precedence than the do/end pair. Essentially, the braces are stickier in an expression. This difference usually rears its head only when you start dropping all of those optional parentheses. a Proc, you just make sure that the value you want returned is computed by the last expression in the Proc object. Whatever value is returned by the Proc is passed back through by call. Thus the code return_24 = lambda {24} puts(return_24.call) will print 24 You can also define parameters for your Proc object, although the syntax is a lit- tle strange. Instead of wrapping your parameter list with the usual round parentheses “()”, you open and close the list with a vertical bar character “|”: multiply = lambda {|x, y| x * y} This code defines a Proc object that takes two parameters, multiplies them together, and returns the result. To call a Proc with parameters, we simply add the parameters to the call method: n = multiply.call(20, 3) puts(n) n = multiply.call(10, 50) puts(n) Running this code will produce the following printout: 60 500 The ability to pass around blocks of code is so useful that Ruby has defined a spe- cial shorthand syntax for it. If we want to pass a block of code into a method, we sim- ply append the block to the end of the method call. The method can then execute the code block by using the yield keyword. For example, here is a method that prints a message, executes its code block, and prints a second message: 86 Chapter 4. Replacing the Algorithm with the Strategy def run_it puts("Before the yield") yield puts("After the yield") end And here is the call to run_it. Notice that we just append the code block to the end of the method call: run_it do puts('Hello') puts('Coming to you from inside the block') end When we slap a code block on the end of a method call as we did above, the block (actually the Proc object) is passed along as a sort of invisible parameter to the method. The yield keywordwill then execute that parameter. For instance, running the code above will produce the following output: Before the yield Hello Coming to you from inside the block After the yield If the block that is passed into your method takes parameters, you supply the parameters to the yield. For instance, the code def run_it_with_parameter puts('Before the yield') yield(24) puts('After the yield') end run_it_with_parameter do |x| puts('Hello from inside the proc') puts("The value of x is #{x}") end Procs and Blocks 87 will print Before the yield Hello from inside the proc The value of x is 24 After the yield Sometimes we want to make the code block parameter explicit—that is, we want to capture the block passed into our method as a Proc object in an actual parameter. We can do so by adding a special parameter to the end of our parameter list. This spe- cial parameter, which is preceded by an ampersand, is assigned the Proc object cre- ated from the code block that came after the method call. Thus an equivalent rendition of the run_it_with_parameters method is def run_it_with_parameter(&block) puts('Before the call') block.call(24) puts('After the call') end The ampersand works in the other direction, too. If we have a Proc object in a variable and we want to pass it to a method that is looking for a code block, we can convert the Proc object back into a code block by sticking an ampersand in front of it: my_proc = lambda {|x| puts("The value of x is #{x}")} run_it_with_parameter(&my_proc) Quick-and-Dirty Strategies What does all of this code block and Proc stuff have to do with the Strategy pattern? Simply put, you can look at a strategy as a lump of executable code that knows how to do something—format text, for example—and is wrapped up in an object. This should sound familiar because it is also a good description of a Proc—a chunk of code wrapped in an object. Recasting our report formatting example to use a Proc strategy is trivial. The only changes we need to make to the Report class are to add an ampersand to pick up any code block that is passed in the initialize method and to rename the method that we call from output_report to call: 88 Chapter 4. Replacing the Algorithm with the Strategy class Report attr_reader :title, :text attr_accessor :formatter def initialize(&formatter) @title = 'Monthly Report' @text = [ 'Things are going', 'really, really well.' ] @formatter = formatter end def output_report @formatter.call( self ) end end Building the formatters is a little different, however. We now need to create Proc objects instead of instances of our special formatter classes: HTML_FORMATTER = lambda do |context| puts('') puts(' ') puts("#{line}
" ) end puts(' ') puts With our new Proc-based formatters in hand, we are ready to create some reports. Given that we have a Proc object and the Report constructor expects a code block, we need to stick an ampersand in front of our Proc object when we create a new Report instance: report = Report.new &HTML_FORMATTER report.output_report Why bother with a Proc-based strategy at all? For one thing, we do not have to define special classes for our strategy—we just wrap the code in a Proc. More importantly, we Quick-and-Dirty Strategies 89 can now create a strategy out of thin air by passing a code block right into the method. As an example, here is our plain text formatter, recast as an on-the-fly code block: report = Report.new do |context| puts("***** #{context.title} *****") context.text.each do |line| puts(line) end end If you are not used to them, code blocks can seem a little bizarre. But consider that with code blocks, we have simplified the Strategy pattern from a context, a base strategy class, some number of concrete strategies, and associated instances here and there, to a context class and some code blocks. Does all of this mean we should just forget about the class-based strategies? Not really. Code block-based strategies work only when the strategy interface is a simple, one-method affair. After all, the only method that we can call on a Proc object is call. If you need more than that for your strategy, by all means build some classes. But if your requirement calls for a simple strategy, the code block may just be the way to go. Using and Abusing the Strategy Pattern The easiest way to go wrong with the Strategy pattern is to get the interface between the context and the strategy object wrong. Bear in mind that you are trying to tease an entire, consistent, and more or less self-contained job out of the context object and delegate it to the strategy. You need to pay particular attention to the details of the interface between the context and the strategy as well as to the coupling between them. Remember, the Strategy pattern will do you little good if you couple the con- text and your first strategy so tightly together that you cannot wedge a second or a third strategy into the design. The Strategy Pattern in the Wild The rdoc utility, which came packaged with your Ruby distribution, contains a cou- ple of instances of the classic GoF class-based Strategy pattern. The purpose of rdoc is to extract documentation from programs. Besides Ruby, rdoc can distill documen- tation from C and (goodness help us!) FORTRAN programs. The rdoc utility uses the 90 Chapter 4. Replacing the Algorithm with the Strategy Strategy pattern to handle each of the different programming languages—there is a C parser, a Ruby parser, and a FORTRAN parser, each of which is a strategy for han- dling its respective input. The rdoc utility also gives you a choice of output formats—you can choose to output your documentation in several flavors of HTML, or in XML, or in the format used by the Ruby-supplied ri command. As you have probably guessed, each of these output formats is also handled by its own strategy. The relationship between the various rdoc strategy classes is a good illustration of the typical Ruby attitude toward inheritance. The class relationship between the various strategies is depicted in Figure 4-2. As you can see from Figure 4-2, there are four related output strategies (or gener- ators, as rdoc calls them) and one stand-alone strategy. The four related strategies all generate similar output—that familiar