Open-Closed Principle

In this episode, Ben and Joe discuss the Open Closed Principle. Also mentioned was polymorphism, the composite design pattern, decorators, factory method, and chain of responsibility. Finally, you might be interested in the immutable data structu...
This is a companion discussion topic for the original entry at https://thoughtbot.com/upcase/videos/open-closed-principle

One thought I had on this episode is that the decorator mentioned in the Printer example is particularly useful for changing the interface of an object from a library that we don’t control:

class Printer
  def initialize(item)
    @item = item
  end

  def print
    puts @item
  end
end

Ruby’s puts method implicitly prints the return value of the #to_s method on the object.

We previously needed to print these types:

Text.new.to_s
Image.new.filename
Document.new.formatted

One option would be to not care about modifying those classes. I would first grep the project for instances of Image and Document and examine how they are being used. Assuming that search makes me feel like this change is possible, I would probably add an Image#to_s and change Document#formatted to Document#to_s, then run all the specs and see if there are any failures.

What if Document came from a library like Prawn or DocRaptor, though? I may not have control over modifying the class (beyond monkey patching, which I try to avoid). This is the case where a decorator would be great:

class PrintableDocument < SimpleDelegator
  def initialize(*args)
    super(Document.new(args))
  end

  def class
    __getobj__.class
  end

  def to_s
    __getobj__.formatted
  end
end

There’s more detail on decorators in Ruby in Evaluating Alternative Decorator Implementations in Ruby, Tidy Views and Beyond with Decorators, and Decorators Compared to Strategies, Composites, and Presenters.

Hello.

Open/Closed isn’t about compiling, it is about change. If you can make a class, module, or method extendable without the need to edit the file itself, you have a win, even without the compilation part.

Let me give you an example i recently wrote:

module Spider
  extend self

  def crawl(identity)
    crawl_method = "crawl_#{identity.provider.to_s}"
    protect_against_unsupported_messages(crawl_method)
    send("crawl_#{identity.provider.to_s}", identity)
  end

protected

  def protect_against_unsupported_messages(method)
    ..
  end

  def crawl_facebook(identity)
    ...
  end
end

This is an example of OCP on the method. We can extend the method all day long without modifying it. Instead, we just add a new private method to handle new providers, like so:

...
  def crawl_facebook(identity)
    ...
  end

  def crawl_twitter(identity)
    ..
  end
end

Now we have added new functionality without changing the public method!

However we still edited the module to change it, so let’s move onto OCP on a whole file/class.

class Animal; end

So if I wanted to add new methods onto the current animal definition:

Animal.exend(Tiger)

If I wanted to add new methods to this instance of animal:

Animal.new.include(Tiger)

And all of this is done in a new file, not animal.rb, thus extending functionality without modifying the existing code. Or Open/Closed Principle.

Hope that helps!

1 Like