Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory methods in Ruby

Tags:

ruby

factory

What is the slickest, most Ruby-like way to have a single constructor return an object of the appropriate type?

To be more specific, here's a dummy example: say I have two classes Bike and Car which subclass Vehicle. I want this:

Vehicle.new('mountain bike')  # returns Bike.new('mountain bike')
Vehicle.new('ferrari')        # returns Car.new('ferrari')

I've proposed a solution below, but it uses allocate which seems way too implementation-heavy. What are some other approaches, or is mine actually ok?

like image 894
Peter Avatar asked Oct 04 '09 04:10

Peter


People also ask

What are factory methods in Ruby?

Factory method is a creational design pattern which solves the problem of creating product objects without specifying their concrete classes. The Factory Method defines a method, which should be used for creating objects instead of using a direct constructor call ( new operator).

Which are the three types of Factory Method?

the abstract factory pattern,the static factory method, the simple factory (also called factory).

What is Factory Method used for?

Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code.


2 Answers

If I make a factory method that is not called1new or initialize, I guess that doesn't really answer the question "how do I make a ... constructor ...", but I think that's how I would do it...

class Vehicle
  def Vehicle.factory vt
    { :Bike => Bike, :Car => Car }[vt].new
  end
end

class Bike < Vehicle
end

class Car < Vehicle
end

c = Vehicle.factory :Car
c.class.factory :Bike

1. Calling the method factory works really well in this instructional example but IRL you may want to consider @AlexChaffee's advice in the comments.

like image 180
DigitalRoss Avatar answered Oct 15 '22 18:10

DigitalRoss


I did this today. Translated into vehicles it would look like this:

class Vehicle
  VEHICLES = {}

  def self.register_vehicle name
    VEHICLES[name] = self
  end

  def self.vehicle_from_name name
    VEHICLES[name].new
  end
end

class Bike < Vehicle
  register_vehicle 'mountain bike'
end

class Car < Vehicle
  register_vehicle 'ferrari'
end

I like that the labels for the classes are kept with the classes themselves, instead of having information about a subclass stored with the superclass. The constructor is not called new, but I see no benefit in using that particular name, and it would make things trickier.

> Vehicle.vehicle_from_name 'ferrari'
=> #<Car:0x7f5780840448>
> Vehicle.vehicle_from_name 'mountain bike'
=> #<Bike:0x7f5780839198>

Note that something needs to make sure those subclasses are loaded before vehicle_from_name is run (presumably these three classes would be in different source files), otherwise the superclass will have no way of knowing what subclasses exists, i.e. you cannot depend on autoload to pull those classes in when running the constructor.

I solved this by putting all subclasses in e.g. a vehicles subdirectory and adding this to the end of vehicle.rb:

require 'require_all'
require_rel 'vehicles'

Uses the require_all gem (found at https://rubygems.org/gems/require_all and https://github.com/jarmo/require_all)

like image 36
clacke Avatar answered Oct 15 '22 18:10

clacke