Pat Maddox
5/10/2007 7:17:00 AM
On 5/10/07, Enrique Comba Riepenhausen <ecomba@mac.com> wrote:
> Hi everyone,
>
> as you know I started a few days ago the Ruby Patterns Wiki and now I
> am starting to put in some samples. As you know, although I am not
> new to software development, I am still a newbie in Ruby.
>
> I was thinking about the Factory Method Pattern and following came to
> my mind. If we follow the Pattern by the book (GoF) we would have
> following code (simplified for the purpose of clarity):
>
> class Product
> def hi
> "hello there"
> end
> end
>
> class ConcreteProduct < Product
> def hi
> "Hello from the concrete implementation"
> end
> end
>
> class Creator
> def create_product
> end
> end
>
> class ConcreteCreator < Creator
> def create_product
> ConcreteProduct.new
> end
> end
>
> Obviously this code strikes in Ruby (specially the creator class that
> doesn't make much sense to me). How would you people think this could
> be implemented in Ruby? Or better to say, what is the Ruby way to
> solve this intent of the Factory Method Pattern: Define an interface
> for creating an object, but let subclasses decide which class to
> instantiate. Factory Method lets a class defer instantiation to
> subclasses.
One clue is that the example is coded in Java, vs smalltalk. The
reason for that is that Smalltalk doesn't really have the problem
they're solving here. An "interface" is just going to be a bunch of
methods with the same method names and signatures.
I'm sure they discusss it in DP, but you don't need to implement this
pattern with an abstract class. That's just there because you want
common behavior. So how do you implement this pattern in Ruby?
class AmericanCoffeeShop
def make_coffee
Coffee.new :ingredients => [ :water, :mud ]
end
end
class ItalianCoffeeShop
def make_coffee
Espresso.new
end
end
Simple, you've implemented the factory method pattern. We have an
interface - make_coffee - and each class can instantiate an object of
whatever kind it wants.
If some client code needs to have a coffee made, you can just pass it
an instance of either of those classes and it'll run fine.
Shared behavior can be provided by a mixin (whereas in Java you need a
superclass).
module CoffeeProvider
def order
take_payment
give_change
make_coffee
end
end
class AmericanCoffeeShop
include CoffeeProvider
def make_coffee
Coffee.new :ingredients => [ :water, :mud ]
end
end
class ItalianCoffeeShop
include CoffeeProvider
def make_coffee
Espresso.new
end
end
Now we've achieved the desired behavior. We've defined the high-level
behavior of taking an order, but placed the responsibility on
lower-level implementation details in another class. Now anyone can
come along and write a class that knows how to provide an order of
coffee, all without any foresight from the initial author of
CoffeeProvider.
Pat