Writing an Inherited Class

Problem

You want to create a new class that extends or modifies the behavior of an existing class.

Solution

If you're writing a new method that conceptually belongs in the original class, you can reopen the class and append your method to the class definition. You should only do this if your method is generally useful, and you're sure it won't conflict with a method defined by some library you include in the future.

This code adds a scramble method to Ruby's built-in String class (see Recipe 4.10 for a faster way to sort randomly):

	class String
	 def 
scramble
	 split(//).sort_by { rand }.join
	 end
	end

	"I once was a normal string.".scramble
	# => "i arg cn lnws.Ioateosma n r"

If your method isn't generally useful, or you don't want to take the risk of modifying a class after its initial creation, create a subclass of the original class. The subclass can override its parent's methods, or add new ones. This is safer because the original class, and any code that depended on it, is unaffected. This subclass of String adds one new method and overrides one existing one:

	class UnpredictableString < String
	 def scramble
	 split (//).sort_by { rand }.join
	 end

	 def inspect
	 scramble.inspect
	 end
	end
	str = UnpredictableString.new("It was a dark and stormy night.")
	# => " hsar gsIo atr tkd naaniwdt.ym"
	str
	# => "ts dtnwIktsr oydnhgi .mara aa"

 

Discussion

All of Ruby's classes can be subclassed, though a few of them can't be usefully subclassed (see Recipe 8.18 for information on how to deal with the holdouts).

Ruby programmers use subclassing less frequently than they would in other languages, because it's often acceptable to simply reopen an existing class (even a built-in class) and attach a new method. We do this throughout this book, adding useful new methods to built-in classes rather than defining them in Kernel, or putting them in subclasses or utility classes. Libraries like Rails and Facets Core do the same.

This improves the organization of your code. But the risk is that a library you include (or a library included by one you include) will define the same method in the same built-in class. Either the library will override your method (breaking your code), or you'll override its method (breaking its code, which will break your code). There is no general solution to this problem short of adopting naming conventions, or always subclassing and never modifying preexisting classes.

You should certainly subclass if you're writing a method that isn't generally useful, or that only applies to certain instances of a class. For instance, here's a method Array#sum that adds up the elements of an array:

	class Array	
	 def sum(start_at=0)
	 inject(start_at) { |sum, x| sum + x }
	 end
	end

This works for arrays that contain only numbers (or that contain only strings), but it

	[79, 14, 2].sum # => 95
	['so', 'fa'].sum('') # => "sofa"
	[79, 'so'].sum
	# TypeError: String can't be coerced into Fixnum

Maybe you should signal this by putting it in a subclass called NumericArray or SummableArray:

	class NumericArray < Array
	 def sum
	 inject(0) { |sum, x| sum + x }
	 end
	end

The NumericArray class doesn't actually do type checking to make sure it only contains numeric objects, but since it's a different class, you and other programmers are less likely to use sum where it's not appropriate.[2]

[2] This isn't a hard and fast rule. Array#sort won't work on arrays whose elements can't be mutually compared, but it would be a big inconvenience to put sort in a subclass of Array or leave it out of the Ruby standard library. You might feel the same way about sum; but then, you're not the Ruby standard library.

You should also subclass if you want to override a method's behavior. In the UnpredictableString example, I overrode the inspect method in my subclass. If I'd just modified String#inspect, the rest of my program would have been thrown into confusion. Rarely is it acceptable to override a method in place: one example would be if you've written a drop-in implementation that's more efficient.

See Also

  • Recipe 8.18, "Implementing Class and Singleton Methods," shows you how to extend the behavior of a particular object after it's been created
  • http://www.rubygarden.org/ruby?TheOpenNatureOfRuby


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net