Friday, February 05, 2010

Design patterns : Abstract Factory

The abstract factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It encapsulates the possibility of creation of a suite of "products" which otherwise would have required a sequence of "if .. then .. else" statements. The abstract factory has the responsibility for providing creation services for the entire family of objects. Clients never create the objects directly - they ask the factory to do that for them.

This mechanism makes changing the entiry family of objects easy - because the specific class of factory object appears only once in the application - where it was instantiated. The application can replace the entire family of objects by simply instantiating a different instance of the abstract factory. It also provides for lazy creation of objects.

The participating actors here are the client, the abstract factory, the abstract product, the concrete factory and the concrete product. The abstract factory defines which objects the concrete factory will need to be able to create. The concrete factory must create the correct objects for its context, ensuring that all objects created by the concrete factory are able to work correctly for a given circumstance.

Lets take an example to explain the situation. The AbstractBookFactory specifies that two classes - the AbstractPHPBook and the AbstractMySQLBook will need to be created by the concrete factory. The concreate factory OReillyBookFactory extends the AbstractBookFactory and can create the objects for OReillyPHPBook and OReillyMySQLBook which are correct classes for the context of OReilly.

abstract class AbstractBookFactory
{
  abstract function makePHPBook();
  abstract function makeMySQLBook();
}

//for objects in context of OReilly
class OReillyBookFactory extends AbstractBookFactory
{
  private $context = "OReilly";
  function makePHPBook()
  {
    return new OReillyPHPBook;
  }
  function makeMySQLBook()
  {
    return new OReillyMySQLBook;
  }
}

//for objects in context of Sams
class SamsBookFactory extends AbstractBookFactory
{
  private $context = "Sams";
  function makePHPBook()
  {
    return new SamsPHPBook;
  }
  function makeMySQLBook()
  {
    return new SamsMySQLBook;
  }
}

//Classes for books
abstract class AbstractBook
{
  abstract function getAuthor();
  abstract function getTitle();
}

abstract class AbstractPHPBook
{
  private $subject = "PHP";
}

abstract class AbstractMySQLBook
{
  private $subject = "MySQL";
}

class OReillyPHPBook extends AbstractPHPBook
{
  private $author;
  private $title;

  function __construct()
  {
    $this->author = "OReilly : php author 1";
    $this->title = "OReilly : title for php";
  }

  function getAuthor()
  {
    return $this->author;
  }

  function getTitle()
  {
    return $this->title;
  }
}

class SamsPHPBook extends AbstractPHPBook
{
  private $author;
  private $title;

  function __construct()
  {
    $this->author = "Sams : php author";
    $this->title = "Sams : title for php";
  }

  function getAuthor()
  {
    return $this->author;
  }

  function getTitle()
  {
    return $this->title;
  }
}

class OReillyMySQLBook extends AbstractMySQLBook
{
  private $author;
  private $title;

  function __construct()
  {
    $this->author = "OReilly : MySQL author";
    $this->title = "OReilly : title for MySQL";
  }

  function getAuthor()
  {
    return $this->author;
  }

  function getTitle()
  {
    return $this->title;
  }
}

class SamsMySQLBook extends AbstractMySQLBook
{
  private $author;
  private $title;

  function __construct()
  {
    $this->author = "Sams : MySQL author";
    $this->title = "Sams : title for MySQL";
  }

  function getAuthor()
  {
    return $this->author;
  }

  function getTitle()
  {
    return $this->title;
  }
}

//testing 

echo("Begin\n");
echo("Testing OReillyBookFactory\n");
$bookFactoryInstance = new OReillyBookFactory;
testConcreteFactory($bookFactoryInstance);
echo("----\n");

echo("Testing SamsBookFactory\n");
$bookFactoryInstance = new SamsBookFactory;
testConcreteFactory($bookFactoryInstance);
echo("----\n");

function testConcreteFactory($bookFactoryInstance)
{
  $phpBook = $bookFactoryInstance->makePHPBook();
  echo "php author : ".$phpBook->getAuthor()."\n";
  echo "php title : ".$phpBook->getTitle()."\n";

  $mysqlBook = $bookFactoryInstance->makeMySQLBook();
  echo "mysql author : ".$mysqlBook->getAuthor()."\n";
  echo "mysql title : ".$mysqlBook->getTitle()."\n";
}

//Output

Begin
Testing OReillyBookFactory
php author : OReilly : php author 1
php title : OReilly : title for php
mysql author : OReilly : MySQL author
mysql title : OReilly : title for MySQL
----
Testing SamsBookFactory
php author : Sams : php author
php title : Sams : title for php
mysql author : Sams : MySQL author
mysql title : Sams : title for MySQL
----

Another example using java

interface GUIFactory //Abstract Factory 
{
  public Button createButton();
}

class WinFactory implements GUIFactory //concrete Factory
{
  public Button createButton() 
  {
    return new WinButton();
  }
}

class OSXFactory implements GUIFactory //Concrete Factory
{
  public Button createButton() 
  {
    return new OSXButton();
  }
}

interface Button //Abstract Product 
{
  public void paint();
}

class WinButton implements Button //concrete Product
{
  public void paint() 
  {
    System.out.println("I'm a WinButton");
  }
}


class OSXButton implements Button //concrete Product
{
  public void paint() 
  {
    System.out.println("I'm an OSXButton");
  }
}


class Application //execution free of product type 
{
  public Application(GUIFactory factory)
  {
    Button button = factory.createButton();
    button.paint();
  }
}

public class ApplicationRunner 
{
  public static void main(String[] args) 
  {
    if(args.length != 1)
    {
      System.out.println("usage : java ApplicationRunner <0/1>");
      System.exit(1);
    }
    new Application(createOsSpecificFactory(args[0]));
  }

  public static GUIFactory createOsSpecificFactory(String ostype) 
  {
    if (ostype.equals("0"))
    {
      return new WinFactory();
    } 
    else 
    {
      return new OSXFactory();
    }
  }
}

Output
------

$ java ApplicationRunner 0
I'm a WinButton
$ java ApplicationRunner 1
I'm an OSXButton

No comments: