Design Patterns in PHP – Factory Method and Abstract Factory

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.

Overview

In object oriented programming, object creation – also known as instantiation – is an implied requirement. Objects must at some point be created for use. Obviously, creating objects is not a difficult task and most languages, PHP included, have simple and intuitive syntax for doing so.

When developing larger, more complex systems though, object creation can become difficult. There are situations where different objects may need to be created based on different conditions or based on the context of the object creating it. Creating objects of concrete types explicitly in code can make these situations a nightmare when it comes time to make revisions and additions. When a new class is introduced, you get to follow a trail of code and commence the hours of debugging that will inevitably follow such an endeavor. This is where design patterns come in.

This article will discuss the usage of Factory Method [DP107] and Abstract Factory [DP87] as they pertain to developing applications in PHP using object oriented programming techniques.

{mospagebreak title=Factory Method}

The first pattern used to simplify object instantiation is the Factory Method pattern. The Factory Method pattern defines an interface for object creation but defers the actual instantiation to subclasses. Take, for example, an application that processes Electronic Funds Transfers (ETFs). There are numerous types of ETFs including virtual check, credit card, wire transfer and so on. Using a non-pattern based approach, the application code requesting an ETF object would need to know precisely what subclass of ETF is needed, and it would need to know the context in which that type of ETF is requested. We would end up with code looking something like this:

switch ($etfType) {
  case ETF_VIRTUALCHECK :
    $etf = new VirtualCheck();
  $etf->processCheck();
  break;
  case ETF_CREDITCARD :
    $etf = new CreditCard();
  $etf->chargeCard();
  break;
  case ETF_WIRETRANSFER :
    $etf = new WireTransfer();
  $etf->chargeCard();
  break;
}

Any time we want to add another ETF, we would have to manually update this switch statement anywhere it appeared. We would also have to update any other conditional code that appears. The CreditCard class hinted at above offers the same problem as the ETF itself in that each credit card type (VISA, MasterCard, AMEX) has its own validation scheme and many types have different numerical formats. We would see a similar switch statement to determine what type of credit card we were dealing with either in the CreditCard class constructor or in the switch statement above creates the CreditCard object.

By implementing the Factory Method, we code our application so it only expects a class that conforms to an interface – that is, it has certain methods and properties that can be used to submit an ETF and check whether it failed or succeeded. This promotes loose coupling in the application because you are not binding a concrete subclass to application code. The result is a great increase in flexibility and maintainability. It is easy to add new ETF subclasses and implement them because you are not hard coding the application to expect a specific subclass, just a class with a specific interface. Look at this example.

class ETF {
  var $data;
  function ETF($data) {
    $this->data = $data;
  }
  function process() {}
  function getResult() {}
}

class VirtualCheck extends ETF {}
class WireTransfer extends ETF {}

class ETFFactory {
  function createETF($data) {
      switch ($data['etfType']) {
      case ETF_VIRTUALCHECK :
        return new VirtualCheck($data);;
      case ETF_WIRETRANSFER :
        return new WireTransfer($data);
      default :
        return new ETF($data);
      }
  }
}

$data = $_POST;
$etf = ETFFactory::createETF($data);
$etf->process();

This is a crude implementation but the intent should be clear. Assume the contents of $_POST represent everything you need for the type of ETF that is happening, including a ‘etfType’ that says what sort of ETF you are using. This would come from the user making a selection in a form and filling out the correct information. This implementation provides numerous advantages over the first.

  1. Data validation can be left entirely to the subclasses and occur without interaction from calling code.

  2. Calling code only needs to know of one way to get an ETF object.

  3. Creation logic is encapsulated – the ETFFactory decides what concrete class to create based on the contents of $data['etfType']. Calling application code knows nothing of concrete subclasses of ETF.

  4. If special measures need to be taken, such as creating a specific type of credit card object, this can take place in one location without the calling application code being involved.

Using this approach consolidates creation logic in a single class – ETFFactory. This eliminates duplication in code when an object is created in multiple locations. With the first method, if a class name is changed or a new ETF class is added, we have to modify the ETF creation code everywhere it appears in our application. With the Factory Method implementation, this is not the case. Our calling application code knows of only ETFFactory and ETF. Subclasses of ETF provide the required specialized behaviors to process themselves in the appropriate way but the calling code needs only to use the process() method.

{mospagebreak title=Abstract Factory}

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes [DP87]. This pattern takes the abstraction displayed in the example above to the next level by providing a common factory interface for a given family of objects. The code that actually uses the factory to create objects only expects an object that conforms to the interface of the abstract factory and does not know any details about concrete factory classes.

Using the example from Design Patterns, consider a game that creates a maze. We have a method that knows what to do to create a maze and it will instantiate all the objects we need to construct the maze – components such as rooms, doors and walls. The example below defines the classes involved – method code is left out deliberately since it is not important to this discussion.

class MapSite {
  function enter() {}
}

define(‘North’, 0);
define(‘East’, 1);
define(‘South’, 2);
define(‘West’, 3);

class Room extends MapSite {
  var $mapSite = array();
  var $roomNumber;
 
  function Room($roomNumber) {}
  function getSide($direction) {}
  function setSide($direction, $mapSite) {}
}

class Wall extends MapSite {
  function Wall() {}
  function enter() {}
}

class Door extends MapSite {
  var $room1;
  var $room2;
  var $isOpen;
 
  function Door() {}
  function enter() {}
  function otherSideFrom($room);
}

class Maze {
  function Maze() {}
  function addRoom($room) {}
  // Etc…
}

class MazeGame {
  function createMaze() {
    $aMaze = new Maze();
    $room1 = new Room(1);
    $room2 = new Room(2);
    $aDoor = new Door($room1, $room2);
   
    $room1->setSide(North, new Wall());
    $room1->setSide(East, $aDoor);
    $room1->setSide(South, new Wall());
    $room1->setSide(West, new Wall());           

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

In this example, we define a MapSite class that will act as a base class for anything that could apear in a maze, such as a door, a room, or a wall. We then define the constants North, East, South, and West to be used for tracking the sides or orientation of these MapSite objects. Following the constant definitions are the definitions for the Wall, Room and Door classes. These objects should be obvious in intent.

The setSide() method of the Room class expects the direction of the side and the object to be placed there – any object derived of the class MapSite. This would typically be a Wall or a Door, but it could support more objects easily. The constructor of the Door class expects two Room objects – the door must be aware of the rooms it is connecting. Next we define the Maze class, which is used to represent our actual maze object in code. We use the addRoom() method to attach rooms to the maze.

Finally, we look at the MazeGame class and its createMaze() method. The createMaze() method creates a maze object, two rooms and a door, then defines what objects occupy the sides of the two rooms and attaches them to our Maze object. At this point, we have successfully created a Maze and put some rooms into it.

{mospagebreak title=Drawbacks of the Example}

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.

{mospagebreak title=Conclusion}

The Factory Method and Abstract Factory patterns allow us to build a great deal of flexibility into our applications by abstracting the process of object instantiation and by helping consolidate creational knowledge in the appropriate classes. The Factory Method pattern teaches us how to use methods for object creation rather than directly instantiating objects through client code. This lets the factory method itself do any work it needs to do in creating the object – work that could involve contextual considerations or initializing certain resources in the object. The Abstract Factory class teaches us how to create groups of related objects with a common interface, creating client code that expects neither a specific type of object nor a specific type of factory.

One of the most important concepts in good object oriented design is “design to an interface, not an implementation.” What this means is that you should code your application to expect standard sets of object behaviors but not to expect specific object types. This results in a system of objects that know as little as possible about one another and, so long as the interfaces to these classes do not change, the internals of the classes can vary without adversely affecting other classes in the system.

In the final createMaze() example above, we see this in action on two levels. First, we pass any MazeFactory type object to the method, which is used to instantiate various MapSite objects and load them into a Maze. Second we are working with the Maze and MapSite objects without worrying about their specific class, just their interface – we know a Room needs a room number when we create it and we know we can enter a room. It does not matter to the client code that the room may be an EnchantedRoom or a RoomWithABomb – all it needs to know is how to create it and how to use it. This is the very definition of programming to an interface. The opposite case is true with the first maze example where createMaze() directly creates specific Room, Door, and Wall objects.

The use of these two patterns is a good starting point for programmers just beginning to learn about patterns and good object oriented design. This is the first in what will be a series of articles on design patterns with PHP examples and is intended for developers who are somewhat new to design patterns. Thanks for reading and please don’t forget to leave some feedback in the comments section!

[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye