- A Different Kind of Duck Typing
- The Template Method Strikes Again
- Parameterized Factory Methods
- Classes Are Just Objects, Too
- Bad News: Your Program Hits the Big Time
- Bundles of Object Creation
- Classes Are Just Objects (Again)
- Leveraging the Name
- Using and Abusing the Factory Patterns
- Factory Patterns in the Wild
- Wrapping Up
Parameterized Factory Methods
One problem with successful programs is that they tend to attract an ever-increasing pile of requirements. Suppose your pond simulation is so popular that your users start asking you to simulate plants as well as animals. So you wave your magic code wand and come up with a couple of plant classes:
class Algae def initialize(name) @name = name end def grow puts("The Algae #{@name} soaks up the sun and grows") end end class WaterLily def initialize(name) @name = name end def grow puts("The water lily #{@name} floats, soaks up the sun, and grows") end end
You also modify the Pond class to deal with plants, like this:
class Pond def initialize(number_animals, number_plants) @animals = [] number_animals.times do |i| animal = new_animal("Animal#{i}") @animals << animal end @plants = [] number_plants.times do |i| plant = new_plant("Plant#{i}") @plants << plant end end def simulate_one_day @plants.each {|plant| plant.grow } @animals.each {|animal| animal.speak} @animals.each {|animal| animal.eat} @animals.each {|animal| animal.sleep} end end
You will also need to modify the subclasses to create some flora:
class DuckWaterLilyPond < Pond def new_animal(name) Duck.new(name) end def new_plant(name) WaterLily.new(name) end end class FrogAlgaePond < Pond def new_animal(name) Frog.new(name) end def new_plant(name) Algae.new(name) end end
An awkward aspect of this implementation is that we need a separate method for each type of object we are producing: We have the new_animal method to make frogs and ducks and the new_plant method to create lilies and algae. Having a separate method for each type of object that you need to produce is not too much of a burden if you are dealing with only two types, as in our pond example. But what if you have five or ten different types? Coding all those methods can be, well, tedious.
A different and perhaps cleaner way to go is to have a single factory method that takes a parameter, a parameter that tells the method which kind of object to create. The following code shows yet another version of our Pond class, this time sporting a parameterized factory method—a method that can produce either a plant or an animal, depending on the symbol that is passed in:
class Pond def initialize(number_animals, number_plants) @animals = [] number_animals.times do |i| animal = new_organism(:animal, "Animal#{i}") @animals << animal end @plants = [] number_plants.times do |i| plant = new_organism(:plant, "Plant#{i}") @plants << plant end end # ... end class DuckWaterLilyPond < Pond def new_organism(type, name) if type == :animal Duck.new(name) elsif type == :plant WaterLily.new(name) else raise "Unknown organism type: #{type}" end end end
Parameterized factory methods tend to slim down the code, because each subclass needs to define only one factory method. They also make the whole thing a bit easier to extend. Suppose you need to define a new kind of product, perhaps fish to go in your pond. In that case, you need to modify only a single method in the subclasses instead of adding a whole new method—another example of the virtues of separating the things that change from those that don’t.