In Progress
Unit 1, Lesson 1
In Progress

Handle

What does lobster fishing have in common with software design? More than you might think. In this episode, you’ll learn how to use the Handle pattern to present a natural interface to client code, while hiding implementation architecture details.

Video transcript & code

On a placid sea, a small buoy bobs gently. it's a simple and unremarkable object. But there is more here than meets the eye.

For the crew of a fishing vessel, the buoy marks the spot where a lobster pot rests on the ocean floor. Using the chain the bouy is attached to, they'll pull in the lobster pot, and add its contents to the days haul.

A lobster buoy is an apt metaphor for a recurring pattern in software engineering: the concept of a "handle". A handle is a simple object which refers to a more complex object or group of objects deep beneath an API.

You might be familiar with the handle concept from the context of system calls for working with files. Operating systems use internal data structures to keep track of information about files. At a deeper level the file system has its own data structures for file housekeeping.

Application developers don't want to deal with the complexity of these deep structures. And operating system programmers don't want applications to depend on internal implementation details.

So instead of exposing the structures directly, operating systems abstract file I/O operations behind a filehandle API. the file handle is typically a simple numeric identifier. By itself, the number is meaningless. We say that it is "opaque". But when we invoke system calls and pass the file handle to them, the operating system is able to map that identifier to its internal tables of files. It can then perform the requested operation without exposing any nitty-gritty implementation details.

The concept of a handle has applicability beyond systems programming.

For instance: In episodes #472 and #473, we developed some objects for managing card games. As we learned more about the contextual identity of card objects, where every card must occupy exactly one collection of cards at a time, we settled on a design centered on a Deck object.

The deck provides a closed system in which the card objects exist. We can use it to list cards. We can also use it in conjunction with card objects to move cards between various named collections within the deck.

(By the way, in case something looks unfamiliar, we've made some minor tweaks to these classes since they last showed up.)

require "./cards"

srand(1234)
deck = Deck.new
deck.cards_in_stack(:draw_pile).size # => 52
deck.cards_in_stack(:draw_pile).first(5)
# => [♣7, ♠Q, ♣2, ♣9, ♠Q]
deck.cards_in_stack(:draw_pile).first.move_to(:avdi_hand)
deck.cards_in_stack(:draw_pile).size # => 51
deck.cards_in_stack(:avdi_hand)
# => [♣7]

The Deck interface is usable, clean, and complete. But there are still some drawbacks to it.

  1. There is something a little awkward and unfamiliar about this interface. In the first attempt we made at managing cards, we represented individual piles and hands of cards as separate array objects. Now, all the cards reside in the deck, and we don't so much move them around as assign them to different names. As an API, this takes some getting used to. We can't really draw on our intuitions from working with real cards in order to work with this interface.
  2. The fact that everything centers around the deck object is really an implementation detail. It stems from our need to maintain the integrity and consistency of the card game. And yet, any client code is going to develop direct dependencies on this specific implementation architecture that we've chosen.
  3. Finally, the Deck class shows early signs of turning into a "God object". It's easy to see how card manipulation methods will steadily accumulate on it.

For all these reasons, in Episode #463 we didn't introduce this class alone. We also gave it a helper: the CardStack class.

CardStack = Value.new(:deck, :name) do
  include Enumerable

  class <<self
    alias [] new
  end

  def each(&block)
    deck.cards_in_stack(self).each(&block)
  end

  def to_a
    deck.cards_in_stack(self)
  end

  def next_ordinal
    deck.next_ordinal_for(name)
  end

  def deal(count, to:)
    count.times do
      first.move_to(to)
    end
  end
end

In that episode we glossed over the creation of the CardStack object relatively quickly. Today, we're going to shine a spotlight on it.

The CardStack class is interesting. Typically, we expect an object with a name like "stack" to contain other objects, perhaps using some kind of mutable internal data structure. But our CardStack is an immutable value object. Its only attributes are a reference to a deck , and the name of the stack within that deck.

deck = Deck.new
stack = CardStack[deck, :draw_pile]

Typically, we get a CardStack by sending the stack message to the deck.

deck = Deck.new
draw_pile = deck.stack(:draw_pile)

A CardStack is able to iterate over a subset of cards contained within the Deck. It can manipulate them as well, with methods like deal. But the CardStack itself remains stateless. Instead of being a heavyweight data structure, the CardStack class gives us a lightweight handle on a concept which was formerly just floating around implicitly inside the Deck object.

require "./cards"

srand(1234)
deck = Deck.new
draw_pile = deck.stack(:draw_pile)
draw_pile.take(4)
# => [♣7, ♠Q, ♣2, ♣9]
draw_pile.deal(4, to: :avdi_hand)
deck.stack(:avdi_hand).to_a
# => [♣7, ♠Q, ♣2, ♣9]

From a library implementation point of view, the CardStack class gives us a home for card-subset-specific logic that would otherwise have bloated up the Deck and Card classes. For instance, cards use ordinal numbers to determine their position in a given stack. There's a utility method on the Deck class for getting the next ordinal in a given stack.

class Deck
  # ...
  def next_ordinal_for(stack)
    cards_in_stack(stack).map(&:ordinal).max.to_i + 1
  end
  # ...
end

This is clearly a stack-centric method. If we move this method to the CardStack class, not only does it declutter the Deck class, but since CardStacks are enumerable the implementation is shorter as well.

CardStack = Value.new(:deck, :name) do
  # ...
  def next_ordinal
    map(&:ordinal).max.to_i + 1
  end
  # ...
end

But beyond giving methods like next_ordinal a better home, the CardStack class also enables client code to work with cards in a more natural way—and without being tied to a Deck God object.

For instance, here's a method that takes a hand stack and a flop stack, and calculates all the possible 5-card combinations that could result.

def card_combinations(hand, flop)
  (hand.to_a + flop.to_a).combination(5).to_a
end

This method has no dependency on the core Deck class. All it knows about is the more constrained CardStack interface. Specifically, that a card stack is something that can be turned into an array. It's ignorant of our closed-deck architecture, and just treats its two inputs as collections of cards.

Strictly speaking, the CardStack class is unnecessary. But by giving client code a handle object on the concept of card stacks, we've provided a more natural-feeling interface and reduced coupling to our implementation choices.

By the way, another name for this pattern is the Bridge Pattern, which you'll find documented in the book Design Patterns.

On a placid sea, a small buoy bobs gently. It's a simple and unremarkable object. But it gives us a handle on a deeper and more complex structure. It's a powerful pattern, that's applicable everywhere from lobster fishing, to operating systems, to application domain object models. Happy hacking!

Responses