Factory Pattern Examples

The factory pattern is a software design pattern where an object of a particular type is returned based on some provided information and without specifying the exact class.

Why use the Factory pattern?

Use the factory pattern when you need to provide a way for someone (a consumer or some client code) to use an object without needing to know how that object is created or it’s dependencies.

The consumer only needs to provide some information to get started using the class object and not the exact class their looking for.

Since the Factory pattern removes the need for a consumer to instantiate class objects, the resulting code becomes less coupled and easier to extend.

A simple example

class HandlesSmallFiles {
}

class HandlesLargeFiles {
}

class Factory {
    function generateObject($string) {
        switch($string){
            case "S":
                return new HandlesSmallFiles();
            case "L":
                return new HandlesLargeFiles();
        }
    }
}

$factory = new Factory();
$object = $factory->generateObject("L");
print get_class($object) . "\n";
$object = $factory->generateObject("S");
print get_class($object) . "\n";

And the output is

HandlesLargeFiles
HandlesSmallFiles

The above example is meant to illustrate the factory concept. We provide either an “S” or an “L” as the information about what object we want, and then the factory delivers the corresponding object.

However, the design isn’t great because the Factory class is not open to extension and would require modification. In other words, if you wanted to have a new case to handle medium size files, we would have to edit the Factory class. And this could be painful to regression test all the areas in the application where the Factory class was used.

An example providing a little more flexibility

<?php

interface FileEncrypter {
    function encrypt();
}

class SmallFileEncrypter implements FileEncrypter {
    private $filename;
    function __construct(string $filename) {
        $this->filename = $filename;
    }
    public function encrypt() {
        return "Now encrypting a small file in memory '".$this->filename."'\n";
    }
}

class LargeFileEncrypter implements FileEncrypter {
    private $filename;
    function __construct(string $filename) {
        $this->filename = $filename;
    }
    public function encrypt() {
        return "Now splitting up '".$this->filename."' into small chunks and encrypting each one\n";
    }
}

abstract class FileEncrypterFactory {
    abstract function getFileEncrypter(): FileEncrypter;
    function doFactoryWork() {
        $encrypter = $this->getFileEncrypter();
        return "FilesFactory: " . $encrypter->encrypt();
    }
}

class SmallFileEncrypterFactory extends FileEncrypterFactory {
    private $filename;
    function __construct(string $filename) {
        $this->filename = $filename;
    }
    function getFileEncrypter(): FileEncrypter {
        return new SmallFileEncrypter($this->filename);
    }
}

class LargeFileEncrypterFactory extends FileEncrypterFactory {
    private $filename;
    function __construct(string $filename) {
        $this->filename = $filename;
    }
    function getFileEncrypter(): FileEncrypter {
        return new LargeFileEncrypter($this->filename);
    }
}

function client(FileEncrypterFactory $factory) {
    print "Now running doFactoryWork() on a ".get_class($factory)." object\n"
        . $factory->doFactoryWork();
}

client(new SmallFileEncrypterFactory("smallfile.txt"));
client(new LargeFileEncrypterFactory("largefile.mp4"));

And the output is

Now running doFactoryWork() on a SmallFileEncrypterFactory object
FilesFactory: Now encrypting a small file in memory 'smallfile.txt'
Now running doFactoryWork() on a LargeFileEncrypterFactory object
FilesFactory: Now splitting up 'largefile.mp4' into small chunks and encrypting each one

In the above example, we are encrypting two files, one small text file and one large mp4 file.

And we have two factory classes, SmallFileEncrypterFactory and LargeFileEncrypterFactory, each for handling small and large files respectively. Both are independent of each other, however both implement the FileEncrypterFactory interface.

We also have a SmallFileEncrypter and LargeFileEncrypterclass both implementing the FileEncrypter interface. These two classes represent the objects the factories create.

The flow happens like this

  • Line 60, where the client() function calls SmallFileEncrypterFactory, the doFactoryWork() method is called on that object.
  • doFactoryWork() calls getFileEncrypter() which then returns the SmallFileEncrypter object. So, now our factory has created the intended object to handle encrypting small files.
  • Then that SmallFileEncrypter object’s encrypt() method is run and does the encryption work it needs to do.

The same flow happens for the LargeFileEncrypterFactory call on line 61, resulting in the encrypt() method run on the LargeFileEncrypter object. But in that case the the intent is that a large file is split into smaller chunks and maybe those chunks are swapped to disk unlike the small files that may be encrypted only in memory.

Why take this approach?

  • The client() function only needs to know about the abstract FileEncrypterFactory class and does not need to know about any particular factory; it accepts any type of factory that extends (inherits) the FileEncrypterFactory class.
    • An abstract class was used in this example because that let’s us have just one default implementation of the doFactoryWork() method for all factories.
  • If we wanted to have another factory, perhaps for encrypting .zip files, we could create that new factory and a corresponding ZipFilesFactory class and adjust the calls to client(). No need to adjust the other already created classes.
  • The factory classes only need to know about the FileEncrypter interface and can return any kind of class object that implements File.