Home arrow PHP arrow Page 4 - Design Patterns in PHP - Factory Method and Abstract Factory

Drawbacks of the Example - PHP

Normally, in object oriented programming, object creation is not difficult. But what if your object needs to be created based on different conditions or other matters of context? Then you will spend hours in debugging and updating--unless you know about design patterns. David Fells explains how they work, and uses the creation of a maze to illustrate his points.

TABLE OF CONTENTS:
  1. Design Patterns in PHP - Factory Method and Abstract Factory
  2. Factory Method
  3. Abstract Factory
  4. Drawbacks of the Example
  5. Conclusion
By: David Fells
Rating: starstarstarstarstar / 79
December 20, 2004

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

Now that you understand the example case, we need to talk about the drawbacks with the implementation used in this example. The main roadblock with using this implementation is that the MazeGame object is hard coded to create specific classes of objects - that is, it creates Maze, Wall, Door and Room directly without use of any factory methods. We can improve this design by refactoring our createMaze method to use a MazeFactory object for object instantiation. Here is an example.

class MazeFactory {
  function MazeFactory() {}
  function createMaze() { return new Maze(); }
  function createRoom($roomNumber) { return new Room($roomNumber); }
  function createDoor($room1, $room2) { return new Door($room1, $room2); }
  function createWall() { return new Wall(); }
}

class MazeGame {
  function createMaze() {
    $factory = new MazeFactory();
    $aMaze = $factory->makeMaze();
    $room1 = $factory->makeRoom(1);
    $room2 = $factory->makeRoom(2);
    $aDoor = $factory->makeDoor($room1, $room2);
   
    $room1->setSide(North, $factory->makeWall());
    $room1->setSide(East, $aDoor);
    $room1->setSide(South, $factory->makeWall());
    $room1->setSide(West, $factory->makeWall());           

    $room2->setSide(North, $factory->makeWall());
    $room2->setSide(East, $factory->makeWall());
    $room2->setSide(South, $factory->makeWall());
    $room2->setSide(West, $aDoor);                         
   
    $aMaze->addRoom($room1);
    $aMaze->addRoom($room2);
  }
}

This method is significantly better because it moves creational knowledge out of the createMaze() method and into the MazeFactory class. This will work fine if we only want to use one family of Maze objects, but what if we want to create subclasses of Wall, Door and Room to allow different behaviors? The examples given in Design Patterns use enchanted rooms and rooms with bombs as examples.

An enchanted room could have special behaviors, such as requiring conditions to be met before a door could be opened or closed. A room with a bomb would know what conditions caused a bomb to detonate and would track whether or not the bomb had gone off. If the bomb had gone off, it would keep up with damage to walls from the bomb. If we wanted to use these classes, we would have to parameterize our factory object to check some sort of input condition to know which family of objects to create.

This is where the Abstract Factory pattern comes in. This pattern uses the Factory Method pattern to handle actual object instantiation, but the value of the Abstract Factory pattern comes at a higher level. We code our calling code not only to use factory methods for object creation but to expect a factory object that conforms to a certain interface. This means that we can use different factories - all based on a single abstract factory interface - to create different families of objects. Calling code would only need to expect a class that derives from the original MazeFactory class.

For brevity's sake we will not type out the code to define the subclasses of Room, Wall, and Door, but we will define the subclasses of the MazeFactory object that are used to create enchanted mazes and mazes with bombs in them.

class EnchantedMazeFactory extends MazeFactory {
  function makeRoom($roomNumber) { return new EnchantedRoom($roomNumber); }
  function makeDoor($room1, $room2) { return new EnchantedDoor($room1, $room2); }
}

class BombedMazeFactory extends MazeFactory {
  function makeRoom($roomNumber) { return new RoomWithABomb($roomNumber); }
  function makeWall() { return new BombedWall(); }
}

We can now use different concrete factory classes to create different families of products - in this case, different types of Doors, Walls, and Rooms. We are now left with one last problem - createMaze(), at last glance, is hard coded to create a MazeFactory object. Since createMaze() only needs to create the objects themselves through a standard factory interface, there is no need for the method to ever actually create the factory. We should pass the factory in as an argument to createMaze() and then let the method do its work.

class MazeGame {
  function createMaze($factory) {
    $aMaze = $factory->makeMaze();
    $room1 = $factory->makeRoom(1);
    $room2 = $factory->makeRoom(2);
    $aDoor = $factory->makeDoor($room1, $room2);
   
    $room1->setSide(North, $factory->makeWall());
    $room1->setSide(East, $aDoor);
    $room1->setSide(South, $factory->makeWall());
    $room1->setSide(West, $factory->makeWall());           

    $room2->setSide(North, $factory->makeWall());
    $room2->setSide(East, $factory->makeWall());
    $room2->setSide(South, $factory->makeWall());
    $room2->setSide(West, $aDoor);               
   
    $aMaze->addRoom($room1);
    $aMaze->addRoom($room2);
  }
}

Now in our createMaze() method, no assumptions are made about the type of factory we need. Some other code that is responsible for figuring out what type of factory to create would actually instantiate the MazeFactory and pass it to createMaze(), as in the following example.

$game = new MazeGame();
$game->createMaze(new EnchantedMazeFactory());

After all is said and done, we have created (with a little help from Design Patterns) a very flexible set of classes for producing mazes. It should be noted that in an actual implementation, createMaze() would be using data of some kind to determine what components are required in the maze and the method calls on the various MapSite objects would not be hard coded as they are in the example.



 
 
>>> More PHP Articles          >>> More By David Fells
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: