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?
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).
the abstract factory pattern,the static factory method, the simple factory (also called factory).
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.
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With