DCI with Ruby Refinements

TL;DR - Have your cake and eat it too. Ruby refinements, currently in 2.0 trunk, can cleanly convey DCI role injection and performs right on par with #include-based composition. However, there's some serious caveats to using refinements over #extend.

Recently, refinements was added to Ruby trunk. If you aren't yet familiar with refinements, read Yahuda's positive opinion as well as Charles Nutter's negative opinion. The idea is simple:

module RefinedString
  refine String do
    def some_method
      puts "I'm a refined string!"
    end
  end
end

class User
  using RefinedString

  def to_s
    ''.some_method #=> "I'm a refined string!"
  end
end

''.some_method #=> NoMethodError: undefined method `some_method' for "":String

It's just a means of monkeypatching methods into a class, a (still) controversial topic. In the above example, the User class can access the #some_method method on strings, while this method is non-existent outside the lexical scope of User.

Using Refinements in DCI

Refinements can be used as a means of role-injection in DCI, amongst the many other techniques. I personally like this technique because the intention of the code is clear to the reader. However, it has some serious drawbacks which we'll address a bit later.

Let's say we want to add the method #run to all Users in a given context.

Our User class:

class User; end

Our refinement of the User class:

module Runner
  refine User do
    def run
      puts "I'm running!"
    end
  end
end

In the above refinement, we are adding the #run method to the User class. This method won't be available unless we specifically designate its presence.

Our DCI context:

class UserRunsContext
  using Runner

  def self.call
    User.new.run    
  end
end

Here, we're designating that we would like to use the refinement by saying using Runner. The #run method is then available for us to use within the context trigger, #call.

Pretty clear what's happening, yeah?

I wouldn't go as far as saying it carries the expressiveness of calling #extend on a user object, but it gets pretty darn close. To reiterate, the technique I'm referring to looks like the following, without using refinements:

user = User.new
user.extend Runner
user.run

Benchmarking Refinements

I'm actually pretty impressed on this front. Refinements perform quite well under test. Let's observe a few of role injection: using inclusions, refinements and extensions.

I ran these benchmarks using Ruby 2.0dev (revision 35783) on a MacBook Pro - 2.2 GHz - 8 GB ram.

Check out the source for these benchmarks to see how the data was derived.

#include (source)

Example

class User
  include Runner
end

Benchmarks

> ruby include_bm.rb
         user       system     total       real
include  0.560000   0.000000   0.560000 (  0.564124)
include  0.570000   0.000000   0.570000 (  0.565348)
include  0.560000   0.000000   0.560000 (  0.563516)

#refine (source)

Example

class User; end
class Context
  using Runner
  ...
end

Benchmarks

> ruby refinement_bm.rb
        user       system     total       real
refine  0.570000   0.000000   0.570000 (  0.566701)
refine  0.580000   0.000000   0.580000 (  0.582464)
refine  0.570000   0.000000   0.570000 (  0.572335)

#extend (source)

Example

user = User.new
user.extend Runner

Benchmarks

> ruby dci_bm.rb
     user       system     total       real
DCI  2.740000   0.000000   2.740000 (  2.738293)
DCI  2.720000   0.000000   2.720000 (  2.721334)
DCI  2.720000   0.000000   2.720000 (  2.720715)

The take home message here is simple: #refine performs equally as well as #include although significantly better than #extend. To no surprise, #extend performs worse than both #refine and #include because it's injecting functionality into objects instead of classes, for which we have 1,000,000 and 1, respectively.

Note: You would never use #include in a DCI environment, namely because it's a class-oriented approach.

Separation of Data and Roles

What I enjoy most about the marriage of refinements and DCI is that we still keep the separation between data (User) and roles (Runner). A critical pillar of DCI is the delineation of data and roles, and refinements ensure the sanctity of this concern. The only component in our system that should know about both data and roles is the context. By calling using Runner from within our UserRunsContext, we've joined our data with its given role in that context.

An example of when we break this delineation can be expressed via a more compositional approach, using include:

class User
  include Runner
end

The problem with this approach is the timing in which the data is joined with its role. It gets defined during the class definition, and therefore breaks the runtime-only prescription mandated by DCI. Furthermore, the include-based approach is a class-oriented technique and can easily lead us down a road to fat models. Consider if a User class had all its possible roles defined right there inline:

class User
  include Runner
  include Jogger
  include Walker
  include Crawler
  ...SNIP...
end

It's easy to see how this could grow unwieldy.

Object-Level Interactions and Polymorphism

Another pillar of DCI is the object-level, runtime interactions. Put another way, a DCI system must exhibit object message passing in communication with other objects at runtime. Intrinsically, these objects change roles depending on the particular context invoked. A User might be a Runner in one context (late for work) and a Crawler in another (infant child).

The vision of James Coplien, co-inventor of DCI, is tightly aligned with Alan Kay's notion of object orientation:

“I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages.” - Alan Kay

 

So, as roles are injected into data objects, do refinements satisfy the object-level interactions required by DCI? Debatable.

With refinements, we're scoping our method definitions within the bounds of a class. With modules, we're scoping our methods within the abstract bounds of whatever consumes the module. By defining methods within a module, we're essentially saying, "I don't care who consumes my methods, as long as they conform to a specific interface." Further, in order to adhere to Alan Kay's vision of object orientation, our objects must be dynamically modified at runtime to accommodate for the context at hand. The use of modules and #extend ensures our data objects acquire the necessary role at runtime. Refinements, on the other hand, do not adhere to this mantra.

Along similar lines, let's look at how refinements affect polymorphism. Specifically, we want to guarantee that a role can be played by any data object conforming to the necessary interface. In statically-typed systems and formal implementations of DCI, this is particularly important because you would be defining "methodless roles", or interfaces, for which "methodful roles" would implement. These interfaces act as guards against the types of objects which can be passed around. When we work with refinements and class-specific declarations, we lose the polymorphism associated with the module-based approach. This can be conveyed in the following example:

module Runner
  def run
    puts "I have #{legs} and I'm running!"
  end
end

# The Runner role can be used by anyone who conforms to
# the interface. In this case, anyone who implements the
# #legs method, which is expected to return a number.
User.new.extend Runner
Cat.new.extend Runner
Dog.new.extend Runner

# When we use refinements, we lose polymorphism.
# Notice we have to redefine the run method multiple times for each
# possible data object.
module Runner
  refine User do
    def run
      puts "I have #{legs} and I'm running!"
    end
  end

  refine Cat do
    def run
      puts "I have #{legs} and I'm running!"
    end
  end

  refine Dog do
    def run
      puts "I have #{legs} and I'm running!"
    end
  end
end

The really unfortunate thing about refinements is we have to specify an individual class we wish to refine. We're not able to specify multiple classes to refine. So, we can't do this:

module Runner
  refine User, Cat, Dog do # Not possible.
    def run
      puts "I have #{legs} and I'm running!"
    end
  end
end

But even if we could supply multiple classes to refine, we're displacing polymorphism. Any time a new data object can play the role of a Runner (it implements #legs), the Runner role needs to be updated to include the newly defined data class. The point of polymorphism is that we don't really care what type of object we're working with, as long as it conforms to the desired API. With refinements, since we're specifically declaring the classes we wish to play the Runner role, we lose all polymorphism. That is to say, if some other type, say Bird, conforms to the interface expected of the Runner role, it can't be polymorphically interjected in place of a User.

Wrapping Up

Refinements are a unique approach to solving role injection in DCI. Let's look at some pros and cons of using refinements:

Pros

  • #refine provides a clean syntax for declaring data-role interactions.
  • Refinements perform around 500% better than #extend in DCI.
  • The data objects are clean after leaving a context. Since the refinements are lexically scoped to the context class, when the user object leaves the context, it's #run method no longer exists.

Cons

  • We lose all polymorphism! Roles cannot be injected into API-conforming data objects at runtime. Data objects must be specifically declared as using a role.
  • We can't pass multiple classes into #refine, causing huge maintenance hurdles and a large degree of duplication.
  • We lose the object-level, cell-like interaction envisioned by Alan Kay in which objects can play multiple and sporatic roles throughout their lifecycle.
  • Testing. We didn't cover this, but in order to test refinements, you would need to apply the cleanroom approach with a bit of test setup. In my opinion, this isn't as nice as testing the results of method after using #extend.

While there's certainly some benefits to using refinements in DCI, I don't think I could see it in practice. There's too much overhead involved. More importantly, I feel it's critical to maintain Alan Kay's (and James Coplien's) vision of OO: long-lived, role-based objects performing variable actions within dynamic contexts.

After all this...maybe I should wait to see if refinements even make it into Ruby 2.0 .

Happy refining!

Posted by Mike Pack on 08/22/2012 at 09:13AM

Tags: dci, ruby, refinements


A Review of MVC

Recently on the object-composition mailing list, Trygve Reenskaug, creator of MVC and co-creator of DCI, posted a short reiteration of the MVC pattern. I'm reposting this here because I find it integral to understanding both historical and present day incarnations of this fundamental design pattern. I find this description succinct and meaningful.

It seems like a new framework is born each and every day, many of which take on the MVC label. The general understanding of MVC is being skewed and the term diluted. This is likely a natural shift in pragmatism, something that shouldn't be demonized. I do feel, however, that we must all understand the underlying principles while discussing these loaded patterns. This post is likely the first of a series, either published here or elsewhere, in which I try to bring clarity to the lexicon we employ daily.

For further reading, I recommend Addy Osmani's articles on design patterns, especially Digesting JavaScript MVC - Pattern Abuse Or Evolution and Learning JavaScript Design Patterns - MVC.


"I made the a first implementation and wrote the original MVC reports while I was a visiting scientist at Xerox Palo Alto Research Center (PARC) in 1978/79. MVC was created as an obvious solution to the general problem of giving users control over their information as seen from multiple perspectives. (The example problem was to enable a user to work meaningfully with an activity plan for the construction of a ship, actually a supertanker. I brought with me a tape with such a plan. It had some 190 activities AFAICR. It was said that this was the first real industry data to ever enter the halls of PARC. The value of real data was that I couldn't fake the data to suit my solutions.

I don't think the user was explicitly mentioned in the original reports since the user's importance was understood by everyone. Jim [Coplien] thought otherwise, and renamed MVC to MVC-U to make the user an explicit element in the pattern. I'll give a short story to clarify the meaning of the MVC terms.

Thing.

Consider my dog 'Fido'. Fido exists in the real world and eats and plays and grows old and does whatever dogs do. Fido is a thing in the meaning of my first PARC report. Not considered worth its own letter since there is always a real world outside the program. (TMVC? The 'T' is surely superfluous.)

Model.

I've opened the hood of my computer and can assure you that there are no dogs there. What is there is a description of Fido represented as bits within the components of my computer. All of Fido can never be represented, but the description can cover all interesting aspects: A video clip of Fido as a puppy, a snapshot of Fido as a 5-year old dog, his name and sex, his age, color, and weight, etc. I'm not the only person interested in Fido; the vet is interested in his illnesses, his medication history, his x-rays, etc. All these data items are representations of the real things; they make up the Model.

View.

We could imagine that I had a special probe that permitted me to inspect and toggle the individual bits that make up the data of the Model. Not very user friendly, IMO. So we have a program that transforms the model data into a form that I can see with my eyes, understand, and manipulate with my hands. This program is a View. It displays a field that holds Fido's weight, 15kg. I realize that I have given Fido too much food lately, so I select the '15' and type '19' over it to signify that Fido is getting fat. The model is changed. A View is both input and output. I'm not interested in everything about Fido; the View is also a filter showing the interesting parts only. The vet's view will be different because his interests are different.

Controller.

May be the vet wants to see two views at the same time; one showing name, sex, age, weight, etc. Another showing his specialties such as illnesses, medication, etc. He needs two views on the screen. They are tightly related since they describe the same dog. The Controller sets up and coordinates one or more Views.

User.

The ultimate master of it all is the user. I did not consider it worth its own letter since there is always a user at the top of the importance hierarchy. MVCU? I considered the 'U' as superfluous as the 'T'. Jim thought otherwise. Apparently, there are people who are so fascinated by programming that the programs are their whole world. They don't remember that a program is without value before it is used by an end user for something valuable. So, regrettably, the 'U' is needed and Jim's MVC-U is the better name."

Posted by Mike Pack on 07/04/2012 at 11:56PM

Tags: mvc, design patterns


Exhibit vs Presenter

The exhibit pattern was recently introduced by Avdi Grimm in his book Objects on Rails. During its introduction, he spent a respectable amount of time laying the foundation for the pattern and discussing how it differs from presenters. While his clarification between the patterns felt succinct and useful, it only piqued my interest. Let's explore these patterns more thoroughly.

Decorators

Both the exhibit and presenter patterns are a form of the decorator pattern. That is, a (good) implementation of these patterns will delegate any unknown methods to the underlying object it decorates, they'll be transparent. Remaining transparent also means duck-typed objects respond to the same interface. They have a contract with their surrounding objects in terms of naming and type.

There's many ways to define a decorator, we'll use SimpleDelegator here: 

class Decorator < SimpleDelegator
end

class Car
  def price
    1_000_000
  end
end

car = Car.new
car.price #=> 1000000

decorated_car = Decorator.new(car)
decorated_car.price #=> 1000000

In the above code, passing the car object into a new Decorator will set up the delegation. Any methods called on an instance of the Decorator will be delegated to the underlying object, the car.

The point of this example is to show that a true decorator will delegate any methods it doesn't have defined on itself (in this case #price) onto the object it's decorating (the car). This is important so that, no matter how many decorators you apply to an object, it will always respond to the same interface.

Building on the previous example, let's look at a less trivial (but still quite trivial) example of decorators:

class CarWithHeatedSeats < Decorator
  def price
    super + 5_000
  end
end

car = Car.new
car.price #=> 1000000

car = CarWithHeatedSeats.new(car)
car.price #=> 1005000

We've now defined a CarWithHeatedSeats decorator which will add $5,000 to the price of the car. It does so by first calling #super and requesting the price of the underlying, delegate, car object. Calling #price on the delegate object, car, will return $1,000,000. $5,000 is then added to the price of a normal car and $1,005,000 is the total.

Straightforward, right? Exhibits and presenters are just flavors of decorators.

Exhibit Pattern

So, exhibits are just decorators. Often with a decorator, you'll want to do more than just forward methods onto the delegate object. You'll likely want to add some additional functionality. Such is the case with exhibits. The additional functionality added will extend (but not disrupt) the delegate object.

The primary goal of exhibits is to connect a model object with a context for which it's rendered. This means it's the exhibit's responbility to call the context's rendering method with the model as an argument. In most Rails applications, the "context" will be the view or the controller. For the following examples, we'll create two non-Rails "contexts", a simple text renderer called TextRenderer and it's HTML counterpart, HtmlRenderer.

The exhibhit could also take the responsibility of attaching metadata to an object. In the following example, we'll attach some additional information about an exhibit which is more akin to the model attributes than how the model is rendered.

A key differentiator between exhibits and presenters is the language they speak. Exhibits shouldn't know about the language of the view (eg HTML). Exhibits speak the language of the decorated object. Presenters speak the language of the view.

Let's look at a simple exhibit:

class CarWithTwoDoorsExhibit < Decorator
  def initialize(car, context)
    @context = context
    super(car) # Set up delegation
  end

  def additional_info
    "Some cars with 2 doors have a back seat, some don't. Brilliant."
  end

  def render
    @context.render(self)
  end
end

class TextRenderer
  def render(car)
    "A shiny car! #{car.additional_info}"
  end
end

class HtmlRenderer
  def render(car)
    "A <strong>shiny</strong> car! <em>#{car.additional_info}</em>"
  end
end

car = CarWithTwoDoorsExhibit.new(Car.new, TextRenderer.new)
car.render #=> "A shiny car! Some cars with 2 doors have a back seat, some don't. Brilliant."
car.price #=> 1000000

car2 = CarWithTwoDoorsExhibit.new(Car.new, HtmlRenderer.new)
car2.render #=> "A <strong>shiny</strong> car! <em>Some cars with 2 doors have a back seat, some don't. Brilliant.</em>"

The purpose of this exhibit is to provide metadata (the #additional_info method) as well as call the context's #render method (within the #render method). We're defining two different "contexts", a text environment and a browser environment. We have two rendering classes, TextRenderer and HtmlRenderer to represent these two contexts. Again, in a normal Rails environment, we'll likely deal with two specific rendering contexts: the controller and the view. 

What we're really after with exhibits is polymorphism. Imagine we created a CarWithFourDoorsExhibit as well. We want to treat cars with 2 doors and 4 doors the same. We don't care how many doors the car has, as long as it can render itself properly.

Let's look at how we can render both 2 and 4 door cars polymorphically:

car = Car.new

exhibit = rand(2) == 1 ? CarWithTwoDoorsExhibit : CarWithFourDoorsExhibit

car = exhibit.new(car, TextRenderer.new)
car.render #=> "A shiny car! ..."

To keep things simple, this example uses rand(2) (which will return 0 or 1) to determine the type of exhibit to use. In a real application, the type of exhibit would likely be chosen based on the number of doors on the car. However, we don't really care whether it's a 2 or 4 door car, we just care that it responds to #render and can express itself. Depending on the result of rand(2), the result of car.render could contain information about 2 door cars or 4 door cars.

Exhibits take heavy advantage of polymorphism.

Presenter Pattern

Presenters are also decorators. The main different between presenters and exhibits is their proximity to the view. Presenters live very close to the view layer. In fact, they are meant to be a representation of the delegate object within the view.

Avdi touches on this in Objects on Rails, but the intention of presenters has diverged since its incarnation. Presenters were originally formed as a more composite-oriented object where you feed it multiple objects and it renders those objects in their combined state:

class AvailabilityPresenter
  def initialize(car, dealer)
    @car, @dealer = car, dealer
  end

  def available?
    dealer.cars_in_stock.include?(car)
  end
end

AvailabilityPresenter.new(Car.new, Dealer.new)

Modern day presenters act more like decorators. Typically, they wrap a Rails model and aid in the presentation.

class CarPresenter < Decorator
  def description
    if price > 500_000
      "Expensive!"
    else
      "Cheap!"
    end
  end
end

car = CarPresenter.new(Car.new)
car.price #=> 1000000
car.description => "Expensive!"

The main goal of presenters is to keep logic out of the view. Its secondary goal is to keep related functionality, which would have previously existed in helpers, in close proximity to the relevant objects. Presenters maintain an object-oriented approach to logic in the view.

If you have conditionals in your views, you'll likely benefit greatly from moving that logic to a presenter. 

Presenter + Exhibit

Presenters and exhibits are not mutually exclusive. We can combine these two concepts to create a presenter which contains view-related logic and knows how to render itself polymorphically.

class CarPresenter < Decorator
  def initialize(car)
    exhibit = rand(2) == 1 ? CarWithTwoDoorsExhibit : CarWithFourDoorsExhibit
    super(exhibit.new(car, TextRenderer.new))
  end

  def description
    if price > 500_000
      "Expensive!"
    else
      "Cheap!"
    end
  end
end

car = CarPresenter.new(Car.new)
car.description #=> "Expensive!"
car.render #=> "A shiny car! ..."

In this example, we combine presenters and exhibits to take advantage of both. We use presenters as a representation of the object in the view and to deal with any view-related logic. We use exhibits to manage rendering the object.

Note: We shouldn't use conditionals in the presenter to derive the correct exhibit. This logic should be extracted into a helper module so all conditionals live in one location. A simplified version of this module might look as follows:

module ExhibitHelper
  def self.exhibit(car, context)
    if car.number_of_doors == 2
      CarWithTwoDoorsExhibit.new(car, context)
    else
      CarWithFourDoorsExhibit.new(car, context)
    end
  end
end

The refactored CarPresenter would look like this:

class CarPresenter < Decorator
  def initialize(car)
    super(ExhibitHelper.exhibit(car, TextRenderer.new))
  end

  def description
    if price > 500_000
      "Expensive!"
    else
      "Cheap!"
    end
  end
end

car = CarPresenter.new(Car.new)
car.description #=> "Expensive!"
car.render #=> "A shiny car! ..."

By extracting this logic into a helper, we keep our presenter clean and oriented towards its purpose.

The Power of Decorators

In the above examples, where presenters and exhibits are used in conjunction, we've demonstrated the true power of decorators. Calling CarPresenter.new(Car.new) decorates the new car twice, once with an exhibit and once with a presenter. The beauty, however, is that we can treat the decorated car exactly as we would treat a normal car. Since it's a true decorator (it delegates all unrecognized methods to the delegate object), we can treat it as though it were a car. For instance, the following works:

CarPresenter.new(Car.new).price #=> 1000000

We can continue to compose the desired car with as many decorations as we'd like:

car = Car.new
car.price #=> 1000000

car = CarWithHeatedSeats.new(car)
car.price #=> 100500

car = CarPresenter.new(car)
car.description #=> "Expensive!"
car.render #=> "A shiny 2 door car! ..."
car.price #=> 100500

Decorators are a form of object composition. We can fashion complex objects with composition instead of inheritance, often a desired technique. Keep in mind, however, that composition obfuscates the identity of the delegate object. To get composition to work correctly with Rails requires tricking Ruby into thinking the decorated object is, in fact, the underlying delegate object. By default, decorators disguise the delegate object:

car = Car.new
car.is_a? Car #=> true
car.kind_of? Car #=> true

car = CarPresenter.new(car)
car.is_a? Car #=> false
car.kind_of? Car #=> false

To get decorators to play nice with Rails, a variety of techniques can be applied. BasicObject is a straightforward approach to getting the decorated object to look like the underlying delegate object. This technique permits decorated objects to be used with Rails in places like #form_for or anywhere Rails performs some "magic" based on the object's identity (eg routes).

Happy decoration!

Posted by Mike Pack on 04/30/2012 at 09:59AM

Tags: exhibit, presenter, decorator, patterns