Software Engineering

Factory Method

Factory method is a pattern where you put a creation of an object into a method, specifying the interface that the object expose. It is useful when you have multiple type object with the same interface that you want to use based on the environment.

For example, you need an object that can store the data in the database. Traditionally, you can create the database object and use the method:

func createUser(username)
{
  db = new MySql
  db.insert({
    username: username,
  })
}

The code would run without a problem as long as you will only ever use MySql. But if at certain situation you need to change the database engine into something else, you will need to rewrite the code. Suppose that you need to use a simple file to store the user data, then you will rewrite the code to be like this:

func createUser(username)
{
  db = new PlainText
  db.insert({
    username: username,
  })
}

When the software needs to run in multiple environment, you’re going to add conditional in object creation phase, if it exists in multiple places (and usually it is), you must change the code in all of that places.

func createUser(username)
{
  db = null;
  if config.db_engine = 'mysql' {
    db = new MySql
  } else if config.db_engine = 'plain_text' {
    db = new PlainText
  }

  db.insert({
    username: username,
  })
}

func deleteUser(username)
{
  db = null;
  if config.db_engine = 'mysql' {
    db = new MySql
  } else if config.db_engine = 'plain_text' {
    db = new PlainText
  }

  db.delete({
    username: username,
  })
}

You will quickly add duplicated conditional code every time you need a database object. On the other hand, every time there is a new database type that you want to utilize, you’re going to need to update the code in all of those places, making your code increasingly duplicated and harder to manage.

Instead, you could use a factory method. Factory method is a method that abstracts the object creation, making it easier to maintain. First, define an interface of the object that you need. In this example, you need an object that can insert and delete data into storage called “Database”. Then, return the object that satisfy the interface based on the configuration. Suppose that MySql and PlainText object implements the Database interface:

interface Database {
  insert(data)
  delete(filter)
}

func makeDb(): Database {
  if config.db_engine = 'mysql' {
    return new MySql
  }
  if config.db_engine = 'plain_text' {
    return new FileText
  }
}

func createUser(username)
{
  db = makeDb();

  db.insert({
    username: username,
  })
}

func deleteUser(username)
{
  db = makeDb();

  db.delete({
    username: username,
  })
}

That way you avoid a lot of duplication in the code and make the code easier to maintain when introducing a new database engine that you want to utilize. For example, suppose that now you want to use MongoDb database in the new environment, what you need to do is just to make sure that MongoDb code has the same interface and modify the makeDb method by adding a new condition based on the configuration, that’s it.

interface Database {
  insert(data)
  delete(filter)
}

func makeDb(): Database {
  if config.db_engine = 'mysql' {
    return new MySql
  }
  if config.db_engine = 'plain_text' {
    return new FileText
  }
  if config.db_engine = 'mongodb' {
    return new MongoDb
  }
}

func createUser(username)
{
  db = makeDb();

  db.insert({
    username: username,
  })
}

func deleteUser(username)
{
  db = makeDb();

  db.delete({
    username: username,
  })
}

Further, if you don’t want or cannot modify the code of existing class, you can just simply override the method on the subclass to use new database engine, and use the the new subclass in client code:

interface Database {
  insert(data)
  delete(filter)
}

class UserRepo {
  func makeDb(): Database { .. }
  func createUser(username) { .. }
  func deleteUser(username) { .. }
}

class UserRepoMongoDb extends UserRepo {
  func makeDb(): Database {
    return new MongoDb
  }
}
...

It also helps when you need to treat the object as singleton:

class UserRepo {
  private db

  func makeDb(): Database {
    if this.db == null {
      this.db = new MySql
    }
    return this.db
  }

  func createUser(username) { .. }
  func deleteUser(username) { .. }
}

The benefit of using factory method

Factory method let’s you decouple the logic of object creation from the code implementation that will use that object. This is useful when you need to support multiple type of object of the same interface based on certain condition such as configuration or environment where the program is running.