Factory Pattern

Let’s say you have a program that you want to be extensible through plugins. All these plugins will have a same interface to them but underneath will do different things.

You could do something like this:

from plugins.plugin_a import PluginA
from plugins.plugin_b import PluginB
load_plugins = [ PluginA(), PluginB() ]

for p in load_plugins:
  p.do_something()

However, it would be nice to load these plugins, and any future ones without having to change your existing code. This is where the factory pattern comes in handy.

Ok, this is what the factory class looks like:

class PluginFactory(object):
  registered_plugins = {}

  @classmethod
  def register_plugin(cls, name, plugin):
    PluginFactory.registered_plugins[name] = plugin

  def __init__(self):
    pass

  def create_plugin(self, plugin_type, param):
    return PluginFactory.registered_plugins[plugin_type](param)

  def get_registered_plugins(self):
    return PluginFactory.registered_plugins.keys()

An explanation: That factory has a method register_plugin which is a class method, allowing it to be called without having to have created an instance of the factory first. This method adds the provided plugin to a dictionary with a name as the key. The other main method is the create_plugin method. This creates a new instance of a plugin based on the plugin_type param passed to it, along with the parameter as required by the plugin.

Let’s look at the plugins themselves. A trivial example:

from plugin_factory import PluginFactory

class PluginA(object):
  def __init__(self, param):
    self.__param = param
    pass

  @classmethod
  def name(cls):
    return "PluginA"

  def do_something(self):
    print "I am PluginA doing something: " + self.__param

PluginFactory().register_plugin(PluginA.name(), PluginA)

The important line in the plugin is the last one. The rest of the class just implements the plugin. The last line calls the PluginFactory’s register_plugin method (without needing to create an instance of PluginFactory first). It passes to it the name of its plugin (to be used as the key in the factory) and its own class, PluginA in this case. For the sake of this example we will make a copy of this file and change “PluginA” to “PluginB”

And the main program:

from plugin_factory import PluginFactory
from plugins import *

pf = PluginFactory()

some_parms = [ "1", "8", "6" , "10" ]
registered_plugins = pf.get_registered_plugins()
plugin_instances = []

count = 0
for x in range(0, 2):
  for p in registered_plugins:
    plugin_instances.append(pf.create_plugin(p, some_parms[count]))
    count +=1

for pi in plugin_instances:
  pi.do_something()

OK, so some explanation. Well most of this main is just some code to create some of our plugin for example purposes. We create a plugin factory, ask it for the plugins that are registered and then use these to create a bunch of plugins using the types of the registered plugins. The important stuff to note is that we have no references to PluginA or PluginB anywhere the main code. This means we don’t have to change anything if we create new plugins. Try it, copy PluginB to PluginC (changing all the references in it from PluginB to PluginC) and the main will run fine (with the exception that the count will overflow the some_params array but that’s just because my example is so trivial).

There is one more thing that you need to do to get this to work. The line

from plugins import *

In order for this line to import all our plugins from our plugins directory we need to write the following in the __init__.py file in the plugins directory

import os
import glob
__all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")]

This means that all files ending in .py will be imported with the above import statement.

The output you should see from this code is as follows:

I am PluginA doing something: 1
I am PluginB doing something : 8
I am PluginA doing something: 6
I am PluginB doing something : 10

Hopefully you can see how the design pattern could be really useful.

Code on github

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s