A few weeks ago I wrote about Dependency Injection and how it doesn't really matter in Ruby. On one of the comments for this post Yoni said (in regard to extending classed - both in .Net 3.5 and Ruby):

"Regarding the issue of extending classes (and partial classes) I am very sorry it was ever invented. Developers should be encouraged to divide large classes by decomposing them into decoupled sub-components using design patterns and not by simply splitting them between files. Even though a feature is called "extending" a class it is in violation of the Open Closed Principle..."

that sounds like a serious charge :) So here's another round of  "Ruby does it better" the time featuring Bertrand Meyer's "Open Closed Principle" or OCP for short.
OCP was defined in 1988 in Bertrand's book "Object Oriented Software Construction"  as follows:
Modules should be both open (for extension and adaptation) and closed (to avoid modification that affect clients)
When we work with a language like C# or Java we have several ways to do that - here's what I had to say about it :


It is easy to see the benefit of having a class that answers this principle: When you need to add a requirement, instead of breaking dependent code (and tests) you just extend it somehow and everything is nice and dandy. Furthermore, violating OCP can result in Rigidity,Fragility, and Immobility.

But how do you do that? The obvious (and naïve) answer is inheritance. Every time something needs to change just add a sub-class. The parent class is not changes and voilà. However, if you add sub-classes all the time you'd get "lazy classes" or freeloaders--sub-classes without a real reason for existence not to mention a maintenance nightmare.

Thus, sub-classing is an option but we need to consider carefully where to apply it. Other (more practical ) OCP preserving steps include:

The way I see it Ruby (and other dynamic languages for that matter) just allows us to extend this repertoire by adding a few other options such as:

Singleton methods (that's not a very good name - but the more appropriate name "instance methods" was already taken by another poor naming choice...) which are methods added to a specific instance of a class:

class Foo
  def bar
    puts "foo.bar"
  end
end
   
obj = Foo.new
obj2 = Foo.new
def obj.bar  # redefining bar just for obj
  puts "foo.newbar"
end
obj.bar   # prints foo.newbar
obj2.bar # prints foo.bar


Closures - I already mentioned closures for C# but it existed in dynamic lanaguages for a long time now. closures are sort of like injecting a procedure. Here's a quick sample:

class Foo
  def bar(myProc)   # accepts a callable object
    foobar = "foo.bar"
    myProc.call(foobar) # call the object with a parameter
  end
end
   
obj=Foo.new
printer=Proc.new {|msg| puts msg}

obj.bar(printer)

Meta programming - Ruby has a lot of ways to add and change classes. methods and whatnot. Again. here is a simple example:
class Foo
end
   
obj=Foo.new

# obj.bar - error
Foo.class_eval do           # this simplistic example can also use regular def
    define_method :bar do   # but class_eval can also evaluate strings to create
      puts "foo.bar"        # dynamic methods like setters etc.
    end
end

obj.bar
And there are a few other similar ways.

All of them  are not violating OCP!

OCP is kept since we do not alter the original class. Anyone using the original class not in our context (i.e. not in the modified context) will not be affected by the change. The interface of the class, in the sense that was originally defined is not changed either, and remains stable. Any client that uses the modified or the original class will not have to change it syntax because of the changes we've maid. This is also in-line with the "protected variation" way of looking at OCP :
"Identify points of predicted variation and create a stable interface around them."
While in C# your would maybe want to add a template method or a specialized interface in Ruby you can apply YAGNI and only change the behavior if the need arise

Lastly, we can also extend classes - but OCP lets us do that so again, we are OK.

Nevertheless, we should be careful not to violate Liskov Substitution Principle when we do all that, which I guess is relatively tempting to do if you using Ruby - but that's for another post ...


 
Wednesday, August 29, 2007 8:11:31 PM (GMT Standard Time, UTC+00:00)
Hi Arnon,

I admit I may have thrown OCP at the end of my comment without doing much research into the different interpretations of it and into Ruby. However, I still think two of the three examples of extending a class at runtime violate OCP in some way or another.

By the way, it is of course irrelevant to regard changes in an object's interface which break client code in a dynamic language. Client code cannot be broken because there aren't any compile time checks in a dynamic language.
This brings us to the issue of breaking clients at runtime. While closures are very much OO (just as delegates and the Command Pattern are), singleton methods and meta programming both involve making changes to a class (or instance) definition at runtime. Clients which may function correctly with either the original or the changed version of the object may break (at runtime) as a result of changes made by other clients. I'm not sure how Ruby works exactly but the way I see it there is usually no "modified context" but rather modified class of modified instance. This means other code which references the same instance or instantiates the same class is modified at runtime by some other code that happened to run at the same time...

I certainly am not against changing an object's behavior at runtime (State Pattern like) but these changes should be made only by logic which is encapsulated within the class - otherwise complexity is only increased.
Yoni
Comments are closed.