Posts Tagged ‘Object Orientated Programming’

Ruby Factory Following the Open-Closed Principle

February 17th, 2009 by ScottK | No Comments | Filed in ruby

I had a rather chilling idea recently, how do you create a Ruby factory that follows the Open-Closed Principle. The open closed principle simply states that your code should be open for extension but not modification. More Here. That means unless it’s a true error or needs modification for a new feature, realistically, don’t modify it to support extensions (inheritance).

The processes I want to incorporate where A.) not case statements. Case statements for class resolution are a code smell in and of themselves, plus including new types breaks open/closed principle, B.) to achieve open/closed principle and load dynamically new classes need to be loaded on the fly. The reality was that it was relatively easy to accomplish, and I’m about to show you how.

We’ve all seen the class A inherits B namespace Y, blah blah blah articles. I’m throwing that out. Here’s the deal you go to the pet shop and you are asking the pet store owner for a lion. He has one and gives it to you. So you ask for a tiger, he doesn’t have one immediately so he checks in back; maybe he has one that just came in, or alerts you to the fact he has none.

Do you see the roles here? You are the consumer of the product provided by the pet shop. If the pet shop owner knows they have one in stock then they certainly give one.

If the pet shop owner doesn’t have one in stock then they need to investgate if they have one. /At this point in time if using case statements you need to add case “Tiger” :) /. In affect though that’s like asking the store owner to shut down the shop to research, since you would need to restart the application to take the new changes. That’s also breaking the open/closed principle since you are modifying the program to change a type return and not a feature.

Ok here’s where I ask that you create the PetShop to expand our zoo!

Create a file called pet_shop.rb and place the following:

require './lion'

module PetShop
  def PetShop.factory(arg)
    unless PetShop.const_defined?(arg)
      begin
	require "#{arg.downcase.to_s}.rb"
      rescue
	raise "Giving up since there does not appear to be a class for this module"
      end
    end
  PetShop.class_eval(arg)
  end
end

Now create a lion, which the store owner has in stock. call it lion.rb in the same directory as pet_shop.rb.

module PetShop
  class Lion

  end
end

Let’s take a stop here and evaluate this code.  First, the shop keeper is importing the lion that they know they have. It’d definitely scoped to the PetShop module because as a consumer we need to go there. Certainly you can argue over the “factory” name. I’ve seen it in many forms.

Here’s where it gets good though!

Once you call the module factory method with the type of animal you want to get several checks are performed. The

unless PetShop.const_defined?(arg)

Checks that the store owner already knows about the Lion. since we included it in the first require block then Lion instance is returned to you the consumer. via the:

PetShop.class_eval(arg)

Go ahead try this by firing up irb!

irb(main):001:0> require "pet_shop"
=> true
irb(main):002:0> lion = PetShop.factory("Lion")
=> PetShop::Lion
irb(main):003:0>

Don’t EXIT!!!1

We just proved the factory part and that the shop owner (Factory) knows it can return your specified type. What I still need to show is that you don’t have to modify this code and adhere to the open/close principle, nor do you have to shutdown your application to add new types. Just as you can’t tell the shop owner what type of animals they have and/or have them shut down their business.

So with that and the previous example still running in irb please create this file “tiger.rb” in the same directory as lion.rb and pet_shop.rb.

module PetShop
  class Tiger

  end
end

Clearly this is a brand new file. We didn’t modify the PetShop.factory. Now in the very same terminal that you called the PetShop.factory(’Tiger”) is want you to call:

irb(main):003:0> tiger = PetShop.factory("Tiger")
=> PetShop::Tiger

That specific part works like this:

    unless PetShop.const_defined?(arg)
      begin
         require "#{arg.downcase.to_s}.rb"
      rescue
          raise "Giving up since there does not appear to be a class for this module"
      end
    end

The unless operator makes sure that we have not required the class in the module instance. “Lion” was imported and therefore is true, “Tiger” was not and is therefore false. When the unless sees false it attempts to import the tiger.rb file. Being successful on the import then no exception is thrown and naturally the factory (shop owner) delivers the Tiger.

However calling:

irb(main):004:0> tiger = PetShop.factory("Bunny")
LoadError: no such file to load -- bunny.rb
	from ./pet_shop.rb:7:in `require'
	from ./pet_shop.rb:7:in `factory'
	from (irb):4
	from :0

Because there is no bunny.rb file. Nor was bunny defined in the lion.rb file.

The factory pattern and open/close principle has been around for ages (OOD lifetime), yet I’ve seen so many software applications resort to everything from if/elses to case statements. I know; Refactoring these is such a huge effort that’s it’s so easy to just go along with.

I hope that in this Ruby example that factories really are easily done as well as make your code a whole lot more extensible, as well as a whole lot less refactorable!

Tags: , , , , ,