RSS

CEK.io

Chris EK, on life as a continually learning software engineer.

Public and Private Interfaces (POODR: Chapter 4)

Having recently graduated from the Flatiron School, I am working my way through programming-related reading, beginning with Sandi Metz’s “Practical Object-Oriented Design in Ruby”. Click here for more posts about POODR.

What you see above are two very different communication patterns. Metz describes them as follows:

In the first application, the messages have no apparent pattern. […] In the second application, the messages have a clearly defined pattern. […] The design issue in the first application is not necessarily a failure of dependency injection or single responsibility. Those techniques, while necessary, are not enough to prevent the construction of an application whose design causes you pain. The roots of this new problem lie not in what each class does but with what it reveals.”

“Practical Object-Oriented Design in Ruby” Page 60

Welcome to public and private interfaces. As Metz explains, we can separate out the methods that we expose and reveal (that is, the public interface) from those that we keep private (known as the—wait for it—private interface). A couple other definitions of public and private interfaces:

Public Interfaces Private Interfaces
The methods that make up the public interface of your class comprise the face it presents to the world. They:
  • Reveal its primary responsibility
  • Are expected to be invoked by others
  • Will not change on a whim
  • Are safe for others to depend on
  • Are thoroughly documented in the tests
All other methods in the class are part of its private interface. They:
  • Handle implementation details
  • Are not expected to be sent by other objects
  • Can change for any reason whatsoever
  • Are unsafe for others to depend on
  • May not even be referenced in the tests

What does that actually mean? POODR provides a helpful example of a restaurant kitchen: a customer should simply order their meal and have it served to them—the menu constitutes the public interface—without needing to go behind-the-scenes into the kitchen.

Awesome. Let’s look at a real life example: Tender (Github repo here). The image below depicts the main interface for the user: she is given options of sending or requesting bitcoin, typing a message, and designating an amount. This is the user’s “menu”, her public interface, the graphic at right at the top of this post. Of course, there’s a private interface as well, the kitchen to this menu, the interwoven left graphic.

How does this play out in code? Like this:

(tender_user.rb) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class User < ActiveRecord::Base
  def send_bitcoin(recipient, amount, message)
    uri = # Coinbase API uri
    self.class.post(# uri with necessary params)
    debit_balance(amount)
    credit_balance(recipient, amount)
  end

  def request_bitcoin(recipient, amount, message)
    uri = # Coinbase API uri
    self.class.post(# uri with necessary params)
  end

  private
  def debit_balance(amount=default_amount)
    # ...
  end

  def credit_balance(recipient, amount=default_amount)
    # ...
  end

  def urlify(amount)
    # ...
  end

  def default_send_amount
    # ...
  end

  def default_request_amount
    # ...
  end

  def check_balance(amount)
    # ...
  end

  # ...Six other methods...

end

Without getting into the details of each method, what this structure demonstrates is a public interface that exposes just the send_bitcoin and request_bitcoin methods. This is all the menu that the User needs. Of course, behind the scenes there are lots of private methods being called, to say nothing of the controller actions delegating those methods, but the user only needs these two.

It comes down to creating explicit interfaces. As Metz argues,

Your goal is to write code that works today, that can easily be reused, and that can be adapted for unexpected use in the future. Other people will invoke your methods; it is your obligation to communicate which ones are dependable.

Every time you create a class, declare its interfaces. Methods in the public
interface should


  • Be explicitly identified as such

  • Be more about what than how

  • Have names that, insofar as you can anticipate, will not change

  • Take a hash as an options parameter
  • “Practical Object-Oriented Design in Ruby” Page 76

The code in the User model above was my attempt to do these things and thus apply practical techniques of object-oriented design.