- 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
Bundles of Object Creation
One problem with our new Habitat class is that it is possible to create incoherent (not to mention ecologically unsound) combinations of fauna and flora. For instance, nothing in our current habitat implementation tells us that tigers and lily pads do not go together:
unstable = Habitat.new( 2, Tiger, 4, WaterLily)
This may not seem like much of a problem when you are dealing with just two kinds of things (plants and animals, in this case), but what if our simulation was much more detailed, extending to insects and birds and mollusks and fungi? We certainly don’t want any mushrooms growing on our lily pads or fish floundering away in the boughs of some jungle tree.
We can deal with this problem by changing the way we specify which creatures live in the habitat. Instead of passing the individual plant and animal classes to Habitat, we can pass a single object that knows how to create a consistent set of products. We will have one version of this object for ponds, a version that will create frogs and lily pads. We will have a second version of this object that will create the tigers and trees that are appropriate to a jungle. An object dedicated to creating a compatible set of objects is called an abstract factory. In fact, the Abstract Factory pattern is yet another of those patterns made famous by the GoF. The code below shows two abstract factories for our habitat simulation, one for the jungle and one for the pond:
class PondOrganismFactory def new_animal(name) Frog.new(name) end def new_plant(name) Algae.new(name) end end class JungleOrganismFactory def new_animal(name) Tiger.new(name) end def new_plant(name) Tree.new(name) end end
After a few simple modifications, our Habitat initialize method is ready to begin using the abstract factory:
class Habitat def initialize(number_animals, number_plants, organism_factory) @organism_factory = organism_factory @animals = [] number_animals.times do |i| animal = @organism_factory.new_animal("Animal#{i}") @animals << animal end @plants = [] number_plants.times do |i| plant = @organism_factory.new_plant("Plant#{i}") @plants << plant end end # Rest of the class...
We can now feed different abstract factories to our habitat, serene in the knowledge that there will be no unholy mixing of pond creatures with jungle denizens:
jungle = Habitat.new(1, 4, JungleOrganismFactory.new) jungle.simulate_one_day pond = Habitat.new( 2, 4, PondOrganismFactory.new) pond.simulate_one_day
Figure 13-2 shows the UML diagram for the Abstract Factory pattern. Here we have two concrete factories, each of which produces its own set of compatible products.
Figure 13-2 The Abstract Factory pattern
The Abstract Factory pattern really boils down to a problem and a solution. The problem is that you need to create sets of compatible objects. The solution is that you write a separate class to handle that creation. In the same way that the Factory Method pattern is really the Template Method pattern applied to object creation, so the Abstract Factory pattern is simply the Strategy pattern applied to the same problem.