...and super . These two keywords allow you to pass control back and forth between parent and child methods, to weave power between a more general method (in the parent class) and a more specific method (in the child class) with ease and logic. Using yield and super effectively can help you maintain the DRY (Don't repeat yourself) principle, keeping your code easier to maintain.
|
|
Say you have three classes for image creation: a parent class, Image , and two children: Raster and Vector . Now for your raster images, you're going to be using the GD2 library for Ruby, and for your vector images, you're going to be creating SVG images. Some of the functionality used to create any given image will be different, naturally, since with the raster images, you're creating an image object, and with the vector images, you're concatenating strings of XML SVG code. However, a lot of the code will be the same, and should thus be shared between the two children to avoid duplication. For example, the logic used to determine what color some text should be, or what shape you should draw, can be shared between the two child classes. Any shared code should go in the one common class between the two children: the parent class, Image .
Accessing Shared Code
That is, accessing code in a parent class method from a child class method of the same name. The trick is with super .
write_text() in Raster
def write_text( text, color )
super() do |width, height, left, top|
image = Image.new( width, height )
image.draw do |canvas|
canvas.color = color
canvas.font = Font::TrueType['/usr/share/fonts/times.ttf', 20]
canvas.move_to( left, top )
canvas.text( text )
end
end
image
end
write_text() in Vector
def write_text( text, color )
image = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
image << '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">'
super() do |width, height, left, top|
image << '<svg viewBox="0 0 ' + width.to_s + ' ' + height.to_s + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
image << '<text font-family="Times" x="' + left.to_s + '" y="' + top.to_s + '" fill="' + color.to_s + '">'
end
image << text.to_s
image << '</text>'
image << '</svg>'
image
end
While these two methods accomplish the same thing, they do it differently. However, each of them has a call to super . That will access a method of the same name in the parent class:
write_text() in Image
def write_text
yield( 300, 100, 5, 5 )
end
All that Image 's write_text() is doing is passing values back to the child method that called it. Each child method's super call defines variables in the block: width, height, left, and top in the line that says super() do |width, height, left, top| . The yield call in Image defines those values to be width = 300, height = 100, left = 5, and top = 5. To Image 's write_text() , however, it's just throwing some numbers at the child method: it doesn't know that the child method is going to use those numbers to define the dimensions of the image and the starting position of its text.
The definition of write_text() in Image is very simple, but it doesn't have to be that way. There could be a great deal of calculation involved in determining what values the child methods should use for whichever variables are being passed to it. The parent class's shared method is a place to store shared logic and shared data; let the child classes do the specific stuff with that general data.
Shared Variables Sans yield
Values can also be reached in the child class methods without using yield if instance variables are defined in the parent method:
write_text() in Image
def write_text
@width = 300
@height = 100
@left = @top = 5
end
Then write_text() in both Raster and Vector would be able to access Image 's @width , @height , @left , and @top , as defined in its write_text() .
Multiple Calls to super
Sometimes it may be useful to let the parent class's method do some work, then the child class's, then the parent's again. In this case, you can call super and yield as many times as necessary:
Method in Child Class
def my_method
return1 = super( 'a string' ) do |my_var|
# Do some stuff with my_var
end
return2 = super( ['array', 'of', 'strings'] ) do |my_other_var|
# Do some stuff with my_other_var
end
end
Method in Parent Class
def my_method( value )
if value.class == String
yield( 'this is my_var' )
elsif value.class == Array
yield( 'this is my_other_var' )
end
# Checks to see if value can work with the <<
# method, which both Array and String objects
# have, and if so, adds another string to it;
# this value is returned since it's the last
# line of the method
value << ' appended to value' if value.respond_to?( :<< )
end
yield Returns Data to super
The child class method can also throw data back to the parent class method by returning a value from the super block:
Method in Child Class
def my_method
value_from_parent = super do |my_var|
'this gets thrown back to the parent class method'
end
end
Method in Parent Class
def my_method
value_from_child = yield( 'becomes my_var' )
'this gets thrown back to the child class method'
end
More Information
|