Amazon.com Widgets Delphi and the Factory Pattern: Factory Methods

Delphi and the Factory Pattern: Factory Methods

By Nick at February 24, 2013 11:16
Filed Under: Delphi, Patterns

I’m currently reading “Head First Design Patterns”, and am finding it very useful and educational.  One problem, though – it’s all in Java.  So I thought that as part of the exercises, I’d translate the code to Delphi.  And also as part of my learning process, I thought it would be a good idea to post an article about each of the patterns.  I also strongly encourage you to buy the book and read it for yourself.

Let me be clear – I’m not doing much more than reproducing the demos in the book.  My purpose is to make the book more approachable for Delphi developers.  The result isn’t always the perfect way to do the pattern since the samples from the book are designed to be as simple as possible to illustrate the point.  I’m very aware that there are better ways to implement the patterns than are shown here.

Introduction

In the first of three posts about the Factory Pattern, we looked at the “Simple Factory” pattern and how it can be used to sequester off your calls to create things.  HFDP didn’t rank the Simple Factory as a full-fledged pattern, but gave it an “Honorable Mention”.  Either way, it’s an effective technique to remove the notion of creating things from your worker classes.  Classes should only do one thing, and Factories do the one thing of creating dependencies so your other classes can do their one thing.

The reason that HFDP doesn’t make the simple factory a full fledged pattern is that they believe it isn’t robust enough to handle variations of the pattern.  It’s great if you have one pizza store.  However, if you want to open a new kind of pizza store – say a store in New York that serves New York-style pizza and then one in Chicago which will server Chicago-style pizza – then you are going to have some seriously ugly case or if statements. 

Branching out to New Pizza Styles

Okay, so the next step is to have different kinds of Pizza depending on where the store is (or, I suppose, the kind of clientele you want to attract, but we’ll just go with regional differences).  First we’ll need a pizza store that can adapt to the different kind of pizzas that the store will need to make. 

Thus we’ll add an abstract  method to TPizzaStore that will create the pizza for us in the descendent classes

  TPizzaStore = class
  protected
    function CreatePizza(aPizzaName: string): TPizza; virtual; abstract;
  public
    function OrderPizza(aPizzaName: string): TPizza;
  end;

The CreatePizza function is abstract, so descendent stores will have to implement it.  That way, each store will get to decide what kind of pizza it will make.  The OrderPizza method makes sure that all pizzas are handled in the same way, but the actual creation will be delegated to the descendent class via CreatePizza.

This is where the factory method part comes in – the TPizzaStore is an abstract class that let’s its descendant decide what kind of pizza it will create.  The call to OrderPizza becomes a “Factory Method”, determining which pizza will be created and thus how each pizza will be prepared.

Next, we’ll create a number of different pizzas that will be created by the specific factory methods.  We’ll create a cheese, pepperoni, clam, and veggie pizza for both the New York and Chicago styles.  (Creating at California-style set of pizzas is left as an exercise for the reader). 

TNewYorkCheesePizza = class(TPizza)
    procedure Prepare; override;
  end;

  TNewYorkPepperoniPizza = class(TNewYorkCheesePizza)
    procedure Prepare; override;
  end;

  TNewYorkClamPizza = class(TNewYorkCheesePizza)
    procedure Prepare; override;
  end;

  TNewYorkVeggiePizza = class(TNewYorkCheesePizza)
    procedure Prepare; override;
  end;

  TChicagoCheesePizza = class(TPizza)
    procedure Prepare; override;
    procedure Cut; override;
  end;

  TChicagoPepperoniPizza = class(TChicagoCheesePizza)
    procedure Prepare; override;
  end;

  TChicagoClamPizza = class(TChicagoCheesePizza)
    procedure Prepare; override;
  end;

  TChicagoVeggiePizza = class(TChicagoCheesePizza)
    procedure Prepare; override;
  end;

You can see the code on BitBucket for the specific implementations of the Pizza classes.  They override some of the methods to provide specific implementations for preparing the pizzas.  For instance, Chicago pizzas are cut in squares and use mozzarella cheese. 

Once the pizzas are available for creation, the specific pizza stores can be created. Below is the declaration and implementation of the TNewYorkPizzaStore.  (The TChicagoPizzaStore looks pretty much identical, except of course that it creates Chicago-style pizzas.)  It uses the OrderPizza method to decide what kind of pizza to create.  Inside OrderPizza is the call to CreatePizza which will get the appropriate pizza type.   Thus, it is a perfect example of a factory method which created things that you need. 

type
  TNewYorkPizzaStore = class(TPizzaStore)
  protected
    function CreatePizza(aPizzaName: string): TPizza; override;
  end; 

... 

function TNewYorkPizzaStore.CreatePizza(aPizzaName: string): TPizza;
begin
  if aPizzaName = 'cheese' then
  begin
    Result := TNewYorkCheesePizza.Create('New York Cheese Pizza');
  end else
  begin
    if aPizzaName = 'pepperoni' then
    begin
      Result := TNewYorkPepperoniPizza.Create('New York Pepperoni Pizza');
    end else
    begin
      if aPizzaName = 'clam' then
      begin
        Result := TNewYorkClamPizza.Create('New York Clam Pizza');
      end else
      begin
        if aPizzaName = 'veggie' then
        begin
          Result := TNewYorkVeggiePizza.Create('New York Veggie Pizza');
        end else
        begin
          raise Exception.Create(aPizzaName + ' is an unknown pizza');
        end;
      end;
    end;
  end;
end;

We still have a great big ugly if statement (There doesn’t seem to be a way to get around that, eh?) but the subclass is the class that decides what kind of pizza gets made.   The TPizzaStore class has no idea what kind of pizza is going to get made when the OrderPizza method is called, which in turn uses the CreatePizza method to return the proper kind of pizza.  That’s the Factory Method pattern to a ‘T’.

So to sum up:  we have an abstract pizza store class that defines how pizzas are ordered and created without knowing what kind of pizza will be created.  The concrete descendants determine what kind of pizzas get created via a factory method – in this case, CreatePizza.

Finally, we can actually create some pizzas using Factory Methods:

procedure MakeMethodPizzas;
var
  ChicagoPizzaStore: uFactoryMethodPizzaStore.TPizzaStore;
  NewYorkPizzaStore: uFactoryMethodPizzaStore.TPizzaStore;
  Pizza: uFactoryMethodPizzaStore.TPizza;
begin
   ChicagoPizzaStore := uFactoryMethodPizzaStore.TChicagoPizzaStore.Create;
   try
     Pizza := ChicagoPizzaStore.OrderPizza('cheese');
     Pizza.Free;
     WriteLn;
     Pizza := ChicagoPizzaStore.OrderPizza('pepperoni');
     Pizza.Free;
     WriteLn;
   finally
     ChicagoPizzaStore.Free;
   end;

   NewYorkPizzaStore := uFactoryMethodPizzaStore.TNewYorkPizzaStore.Create;
   try
     Pizza := NewYorkPizzaStore.OrderPizza('cheese');
     Pizza.Free;
     WriteLn;
     Pizza := NewYorkPizzaStore.OrderPizza('clam');
     Pizza.Free;
     WriteLn;
   finally
     NewYorkPizzaStore.Free;
   end;
end;

 

The pizza store types are prefaced with their unit identifiers to keep them distinct from the pizza class from the abstract factory demo that we’ll see in the next installment of the Factory Pattern demos.

That’s the Factory Method Pattern – you create an abstract class and then descend from it to implement a method for creating the right class.

Next time, we’ll look at how you can create an entirely separate interface for completely abstracting the notion of creating things.

blog comments powered by Disqus

My Book

A Pithy Quote for You

"Courage is not simply one of the virtues, but the form of every virtue at its testing point."    –  C. S. Lewis

Amazon Gift Cards

General Disclaimer

The views I express here are entirely my own and not necessarily those of any other rational person or organization.  However, I strongly recommend that you agree with pretty much everything I say because, well, I'm right.  Most of the time. Except when I'm not, in which case, you shouldn't agree with me.