The first technique we’ll look at in this series is something so basic it may not even seem worth spelling out. But sometimes old-school techniques are overlooked in the excitement of a young language.
Let’s use as our example a hypothetical communications protocol, Flying Monkey Transport Protocol (FMTP). Flying Monkey Transport Protocol is a packet-based peer-to-peer networking protocol in which messages are transported from one peer to another by means of flying monkeys carrying satchels full of data.
As developers in the inter-kingdom IT department, it’s our job to make sure that communications between e.g. the Wicked Witch of the East and the Lollipop Guild flow unimpeded. Where wicked witches are concerned it’s important that no one get mixed messages.
The interface for the Ruby FMTP implementation looks something like this:
module FMTP
class Connection
def initialize(address)
# ...
end
def send(message)
# ...
end
def receive
# ...
end
end
class Message
# ...
attr_reader :data
end
end
Once a connection is initialized, we can receive messages from the opposite peer by calling receive, which returns a @Message@ object:
connection = FMTP::Connection.new("witch.east")
message = connection.receive
Lately the Wicked Witch of the East has gotten rather chatty in her old age, and her messages have been exceeding maximum monkey capacity. As a result, we’ve been forced to start dividing her messages up across multiple monkeys. Unfortunately, the writers of the FMTP library did not plan for this possibility, so recipients of the Witch’s communiques have been getting truncated messages. We’ve been tasked with making the necessary changes in order to support multi-monkey messages.
As good Ruby programmers, we like to exploit the language’s dyanamic features to the max. And at first, this might seem like the perfect opportunity to use Ruby’s capacity for runtime class modification. We’ll just re-open the class and patch it to do what we need:
module FMTP
class Connection
alias_method :receive_without_buffer, :receive
def receive
buffer = ""
begin
message = receive_without_buffer
buffer < < message.data
end until(message.data.include?("ENDENDEND"))
Message.new(buffer)
end
end
end
But there's another way to accomplish the same ends. A simpler, low-tech way: inheritance.
Inheritance has gone somewhat out of fashion in recent years. And not without reason. In the old days inheritance was seen as almost synonymous with object-orientation, and as a result it was frequently abused. Programs would consist of elaborate, many-leveled inheritance heirarchies that resembled an inbred royal family tree. These programs were hard to understand and hard to maintain.
Ruby programmers have, for the most part, learned their lesson well in this regard. I rarely see a Ruby application with more than two layers of inheritance. For the most part this is a good thing. But occasionally the avoidance of inheritance leads to implementing more complex solutions in places where inheritance is a perfectly legitimate technique.
This is one of those cases. Here is how the code would look using inheritance:
class BufferedConnection < Connection
def receive
buffer = ""
begin
message = super
buffer << message.data
end until(message.data.include?("ENDENDEND"))
Message.new(buffer)
end
And here's how it's used:
connection = BufferedConnection.new("witch.east")
message = connection.receive
The difference is small, to be sure. But the inheritance version has a number of advantages. It’s slightly shorter. It’s simpler, because there is no need to alias the original method to a new name; we can just use @super@. The name @BufferedConnection@ makes it obvious that we are using a buffered variant of a @Connection@. There’s no chance of our becoming confused by the disparity between what the original @#receive@ method says, and how it actually behaves. And we know that since we have to explicitly ask for the buffered version, there’s no chance of our inadvertantly breaking code somewhere else in the program by changing the semantics of @Connection@.
It might seem like too obvious a technique to even mention. But it’s easy to forget about the prosaic solutions in a language that gives us so many possibilities. You should still put some thought into whether inheritance isappropriate in any given situation. So long as it is a legitimate IS-A relationship and the “Liskov Substitution Principle”:http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple is satisfied, though, there’s nothing wrong with a little good old-fashioned inheritance.
h3. Applicability
Consider using inheritance when:
* *You* control object creation.
Related posts:



Pingback: Virtuous Code › Sustainable Development in Ruby, Part 2: Method Injection
Pingback: This Week in Ruby (April 7, 2008) | Zen and the Art of Programming
Pingback: Enfranchised Mind » 7 Actually Useful Things You Didn’t Know Static Typing Could Do: An Introduction for the Dynamic Language Enthusiast
Pingback: Sustainable Software Development | humandoing software