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

Delphi and the Factory Pattern: Simple Factory

By Nick at February 16, 2013 22:54
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. 

The chapter on the Factory Pattern in HFDP is pretty long, and divided into three main sections.  I’ll do a post on each section: General discussion and the Simple Factory, the Factory Method Pattern, and the Abstract Factory. 

General Discussion

Regular readers of this blog will know that I’m a huge proponent of Dependency Injection.  I’ve gone so far as to say that Dependency Injection should just be a way of life for a developer.  If you aren’t following the principles of Dependency Injection, you aren’t writing good, clean code.

One of the basics of Dependency Injection is the notion that the creation of objects is a “single responsibility” and that your class shouldn’t take on that responsibility because it, too, should only have a single responsibility.  If a class is taking care of its responsibility and creating the things that it needs, it is doing too much. The notion of creating things is a big responsibility, and not something that should be taken lightly.  Creating things should be done by classes whose specific job it is to create things. 

And one of the basic tenets of development is to code against abstractions and not concrete implementations.  Well, every time you call Create means you are coding against a concrete implementation.  That should be avoided as much as possible, right?  If you press all your Create calls back to a factory, then you can minimize the number of calls to Create and keep them well sequestered away. 

Thus we have the Factory Pattern.  The job of the factory pattern is to remove the worry and concern of creating things and make it happen pretty much automatically.  A while back I even wrote a blog post entitled “Life is Too Short to Call Create”.  In other words, the main job of the Factory Pattern is to hide via encapsulation the process of creating something.  Factories are the main places where your calls to Create should happen.

The Simple Factory

HFDP uses the example of a pizza store to show how a factory might be used to create pizzas.  So following along with their example, we can declare a pizza class and some specific types of pizzas:

type

  TSimplePizza = class(TObject)
    procedure Prepare;
    procedure Bake;
    procedure Cut;
    procedure Box;
  end;

  TCheesePizza = class(TSimplePizza );
TPepperoniPizza = class(TSimplePizza );
TVeggiePizza = class(TSimplePizza );
...

{ TPizza }

procedure TSimplePizza .Bake;
begin WriteLn('Bake the pizza'); end; procedure TSimplePizza .Box;
begin WriteLn('Put the pizza in a box'); end; procedure TSimplePizza .Cut;
begin WriteLn('Cut the pizza'); end; procedure TSimplePizza .Prepare;
begin WriteLn('Prepare the Pizza'); end;

There’s nothing special here, just some classes that represent pizzas.  The pizzas know how to be prepared.  Now, of course, we have to have a place to make our pizzas, so we declare a pizza store that knows how to take a pizza order:

 TSimplePizzaStore = class
    function OrderPizza(aPizzaType: string): TSimplePizza;
  end;

...

function TPizzaStore.OrderPizza(aPizzaType: string): TSimplePizza;
begin
  if aPizzaType = 'cheese' then
  begin
    Result := TCheesePizza.Create;
  end else
  begin
    if aPizzaType = 'pepperoni' then
    begin
      Result := TPepperoniPizza.Create;
    end else
    begin
      if aPizzaType = 'veggie' then
      begin
        Result := TVeggiePizza.Create;
      end else
      begin
        raise Exception.Create('I don''t know what kind of pizza that is: ' + aPizzaType);
      end;
    end;
  end;

  Result.Prepare;
  Result.Bake;
  Result.Cut;
  Result.Box;
end;

Now that works great.  But what happens when you want to add a pizza?  You have to change the OrderPizza method.  But what if you want to do something else to the pizzas?  Say you want to use the pizza class to program your point of sale, and provide pricing and descriptions?  You’d have to do another one of those big ugly if statements there as well, resulting in two places to change the code for a new pizza.  And of course, if you came up with a delivery scheme, that might be a third place. 

The code for creating the pizzas is something that seems likely to change and be duplicated, and so that screams out “Encapsulate me!”.  This is a good example of a class trying to do two things:  order a pizza and create a pizza.  It’s not doing one thing like all good classes that follow the Single Responsibility Principle should.  When a class tries to do two things, it has two reasons to change, and when a class has more than one reason to change, it becomes less useful, more coupled, and more complicated to change. 

So instead of doing the pizza creation right in the OrderPizza method, let’s create a class whose sole job is to create the correct pizza on demand.  This class will be a “simple factory” class, and it will take the pizza creation code out of the OrderPizza method and put it into its own method:

type
  TSimplePizzaFactory = class
    function CreatePizza(aPizzaType: string): TSimplePizza;
  end;
...

{ TSimplePizzaFactory }

function TSimplePizzaFactory.CreatePizza(aPizzaType: string): TSimplePizza;
begin
  if aPizzaType = 'cheese' then
  begin
    Result := TCheesePizza.Create;
  end else
  begin
    if aPizzaType = 'pepperoni' then
    begin
      Result := TPepperoniPizza.Create;
    end else
    begin
      if aPizzaType = 'veggie' then
      begin
        Result := TVeggiePizza.Create;
      end else
      begin
        raise Exception.Create('I don''t know what kind of pizza that is: ' + aPizzaType);
      end;
    end;
  end;
end;

TSimplePizzaFactory is pretty straight-forward:  it creates the kind of pizza you ask it for.  Simple.

Now that we have a class that has the sole responsibility for creating pizzas, we can simplify the TSimplePizzaStore class, passing it a TSimplePizzaFactory:

  TSimplePizzaStore = class
  private
    FFactory: TSimplePizzaFactory;
  public
    constructor Create(aPizzaFactory: TSimplePizzaFactory);
    destructor Destroy; override;
    function OrderPizza(aPizzaType: string): TSimplePizza;
  end;

...

{ TSimplePizzaStore }

constructor TSimplePizzaStore.Create(aPizzaFactory: TSimplePizzaFactory);
begin
  inherited Create;
  FFactory := aPizzaFactory;
end;

destructor TSimplePizzaStore.Destroy;
begin
  FFactory.Free;
  inherited;
end;

function TSimplePizzaStore.OrderPizza(aPizzaType: string): TSimplePizza;
begin
  Result := FFactory.CreatePizza(aPizzaType);

  Result.Prepare;
  Result.Bake;
  Result.Cut;
  Result.Box;
end;

The new pizza store takes the pizza factory as a parameter on its constructor and stores it.  Then the OrderPizza method uses that factory to create the pizza instead of doing the creation itself.  That unpleasant if statement is neatly hidden away in the TSimplePizzaFactory class where it can be re-used by anyone that needs it.  The pizza store just knows that the factory will create the right pizza for it.

So tying it all together, the following code will create a pepperoni pizza:

procedure MakeSimplePizza;
var
  SimpleStore: TSimplePizzaStore;
begin
  SimpleStore := TSimplePizzaStore.Create(TSimplePizzaFactory.Create);;
  try
    SimpleStore.OrderPizza('pepperoni');
  finally
    SimpleStore.Free
  end;
end;

Things to Note

Okay, here are some things to note about all of this:

  • Sure, the code for creating pizzas didn’t really change – we still have that big ugly if statement.  But it is nestled away in a single class – isolated, decoupled, and ready for use anywhere.  It is also a single place to add new pizza types.
  • The code above simply calls Create on the factory as a direct parameter in the constructor for the pizza store.  The pizza store then owns, and thus frees, the factory. 
  • Strictly speaking, this use of a simple factory isn’t a pattern.  Or at least that is what the folks in HFDP argue.  I’m not sure that I agree with them.  This is a pretty simple yet effective pattern in my mind.
  • Now, remember, I’m just implementing the basics of the code in the book.  There are some clear improvements that could be made to this code, and indeed the next two blog articles will cover much of those improvements, but I’ll mention a couple here:
    • First, both TSimplePizza and TSimplePizzaStore (and even TSimplePizzaFactory) could implement interfaces, enabling us to code against interfaces.
    • The CreatePizza method on TSimplePizzaFactory could have been declared as a class static method, making it so that you don’t even have to create an instance of the factory.
    • Passing in the pizza type as a string isn’t perfect either – it should be an enumerated type
  • The code for this project can be found on BitBucket.

Conclusion

So that’s the first look at a simple factory – a class whose job is to create things for you instead of you creating them yourself.  That’s the heart of what Dependency Injection is all about, and it’s a critical part of making sure that your code is as loosely coupled as possible.

blog comments powered by Disqus

My Book

A Pithy Quote for You

"It is difficult to get a man to understand something, when his salary depends upon his not understanding it."    –  Upton Sinclair

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.