9

I need a class that acts like a Hash, though not necessarily with all the Hash methods. I've read that it is not a good idea to subclass core classes like Hash. Whether or not that is true, what is the best practice for doing this kind of thing?

# (a) subclass Hash, add new methods and instance variables
class Book < Hash
   def reindex
     @index = .....
   end
end

# (b) create a new class from scratch, containing a hash, 
#    and define needed methods for the contained hash
class Book
  def initialize(hash)
    @data = hash
  end 
  def []=(k,v)
    @data[k] = v
  end
  # etc....
  def reindex
    @index = ....
  end

# (c) like (b) but using method_missing

# (d) like (b) but using delegation

I realize that Ruby has more than one way to accomplish a given task, but are there any general rules for which of the above methods are preferable in a relatively simple case?

Mike Blyth
  • 4,158
  • 4
  • 30
  • 41
  • 2
    When you only need some methods, I tend towards composition, with delegate. – Dave Newton Jan 06 '13 at 19:03
  • 1
    Why can't you use `Hash`? What goes wrong? Subclassing is used when you want to add or alter methods, not when you want to remove them. – sawa Jan 06 '13 at 19:58
  • @sawa, perhaps I wasn't clear. I do want to add or change methods. My example adds the "reindex" method. – Mike Blyth Jan 06 '13 at 20:48

1 Answers1

15

If I absolutely do not want the Hash-like object to have certain hash methods, then I would wrap the object in my own class and only expose the methods I want it to have (your option b).

If I wanted it to maintain its true hash behavior with some added behavior, I would add that behavior in a module to the hash object itself, not modifying the core hash class:

module SpecialHash
  def reindex
    # method def
  end
end

my_hash = {}
my_hash.extend(SpecialHash)
my_hash.reindex #now is defined on my hash

Most commonly, one of these options will do the trick for me.

In general, I tend to favor using modules to extend class behavior instead of class inheritance because I consider it to be a cleaner and more lightweight approach. Creating a new class always gives me the feeling that I am adding a new "thing" to my domain model. This is fine and is exactly what you want to do in countless scenarios, but Ruby's mixin capabilities give you a very nice alternative when you don't actually need to go quite that far.

The main time when I would deal with creating a class is if there is some additional state in the object that I want to keep track of. If my additions are not about expanding on the state of the object, but are just about expanding on it's behavior, then I will almost always start by mixing that behavior into an existing instance of that class using a module.

Another answer to this sort of question also brings up some other points worth keeping in mind as well: ruby inheritance vs mixins

Community
  • 1
  • 1
Pete
  • 17,885
  • 4
  • 32
  • 30
  • I agree, but in his case option (b) is better. He wants to expose only a fraction of `Hash`'s methods and add new ones. – Carlos Agarie Jan 07 '13 at 04:37
  • Actually, I'm interested in the general case whether or not I want to include all Hash's methods. As it happens at the moment, having them all available is fine. But, addressing Pete's solution, why add the module to an object rather than simply subclassing Hash? Would it not be more straightforward to have "class Book < Hash," adding the methods directly or via a module? – Mike Blyth Jan 07 '13 at 09:10
  • Sorry for the delay, tried to expand on my answer a bit to give a little more insight into "why" I choose the option that I do in this situation. Hope it helps! – Pete Jan 09 '13 at 02:30
  • OK, Pete, that makes sense and the discussion you link to is good. It feels more natural to me to sub-class Hash and add the module, so I can then write "book = Book.new" rather than "book = {}; book.extend(BookBehavior)" but maybe the latter is a clear expression of what is going on. – Mike Blyth Jan 12 '13 at 10:36